From a5a5beb5976c3e5f897fb50faddd356f41248ba9 Mon Sep 17 00:00:00 2001 From: sblowers-aridhia Date: Fri, 3 Jul 2020 10:14:12 +0100 Subject: [PATCH 01/51] moved contents to submodules --- .gitmodules | 9 + api/common_api.yaml | 626 ------------------------------ api/task_execution.openapi.yaml | 654 -------------------------------- common-api-metadata | 1 + common-api-selection | 1 + common-api-tasks | 1 + 6 files changed, 12 insertions(+), 1280 deletions(-) create mode 100644 .gitmodules delete mode 100644 api/common_api.yaml delete mode 100644 api/task_execution.openapi.yaml create mode 160000 common-api-metadata create mode 160000 common-api-selection create mode 160000 common-api-tasks diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f2f13dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "common-api-metadata"] + path = common-api-metadata + url = https://github.com/federated-data-sharing/common-api-metadata +[submodule "common-api-selection"] + path = common-api-selection + url = https://github.com/federated-data-sharing/common-api-selection +[submodule "common-api-tasks"] + path = common-api-tasks + url = https://github.com/federated-data-sharing/common-api-tasks diff --git a/api/common_api.yaml b/api/common_api.yaml deleted file mode 100644 index d2a19b4..0000000 --- a/api/common_api.yaml +++ /dev/null @@ -1,626 +0,0 @@ -openapi: 3.0.0 - -info: - title: Common API for Federated Data Sharing - description: | - A set of APIs to support different levels of data sharing in a federated network. - The API has three parts Metadata, Selection and Compute. A site must implement the Metadata API, - and it can either implement the Selection API or the Compute API. In the case that the Compute API - is implemented, the Selection API must be implemented as input to compute tasks. Currently, the Compute API endpoints are omitted as they are under review. - version: "1.1.0" - -externalDocs: - description: Common API for Federated Data Sharing Github repository - url: https://github.com/federated-data-sharing/common-api - - -paths: - /datasets: - get: - summary: Get a list of available datasets. - description: Shows the list of all datasets available for querying. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/dataset_list" - "401": - description: Unauthorized (401) - - tags: - - Metadata API - - - /datasets/{datasetid}: - get: - summary: Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. - description: Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). - - parameters: - - name: datasetid - in: path - description: Dataset ID - required: true - style: simple - explode: false - schema: - type: string - example: - "hospital_a_patient_results" - - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/dataset_information" - "401": - description: Unauthorized (401) - - tags: - - Metadata API - - - /datasets/{datasetid}/catalogue: - get: - summary: Get Catalogue entry (metadata) for dataset. - description: Returns the catalogue metadata for a specified dataset (by dataset ID). - - parameters: - - name: datasetid - in: path - description: Dataset ID - required: true - style: simple - explode: false - schema: - type: string - example: - "hospital_a_patient_results" - - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DCAT_metadata" - "401": - description: Unauthorized (401) - "404": - description: Not Found (404) - Dataset not found. - - tags: - - Metadata API - - - /datasets/{datasetid}/dictionaries: - get: - summary: Get Dictionaries (field descriptions) for dataset. - description: Returns a list of field descriptions for each table within a specified dataset (by dataset ID). - - parameters: - - name: datasetid - in: path - description: Dataset ID - required: true - style: simple - explode: false - schema: - type: string - example: - "hospital_a_patient_results" - - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/data_dictionary_list" - "401": - description: Unauthorized (401) - "404": - description: Not Found (404) - Dataset not found. - - tags: - - Metadata API - - - /datasets/{datasetid}/dictionaries/{tableid}: - get: - summary: Get a single dataset Dictionary for a specified table. - description: Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). - - parameters: - - name: datasetid - in: path - description: Dataset ID - required: true - style: simple - explode: false - schema: - type: string - example: - "hospital_a_patient_results" - - name: tableid - in: path - description: Table ID - required: true - style: simple - explode: false - schema: - type: string - example: - "table_1" - - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/data_dictionary" - "401": - description: Unauthorized (401) - "404": - description: Not Found (404) - Dataset not found. - - tags: - - Metadata API - - - /selection/validate: - post: - summary: Validate a given selection query. - description: With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. - - requestBody: - description: GraphQL Query - content: - text/plain: - schema: - type: string - example: "{hospital_b_patient_results {table_1 {sex, age}}" - required: true - - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/selection_success" - 400: - description: Bad Request (400) - The server cannot process the request. Please check your request body. - 401: - description: Unauthorized (401) - 404: - description: Not Found (404) - Dataset not found. - - tags: - - Selection API - - - /selection/beacon: - post: - summary: Get a Beacon (T/F) for a specified data selection. - description: With a simple Graph QL query, check which locations contain data relevant to a specific query. - - requestBody: - description: GraphQL Query - content: - text/plain: - schema: - type: string - example: "{hospital_b_patient_results {table_1 {sex, age}}" - required: true - - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/selection_success" - 400: - description: Bad Request (400) - The server cannot process the request. Please check your request body. - 401: - description: Unauthorized (401) - 404: - description: Not Found (404) - Dataset not found. - - tags: - - Selection API - - - /selection/select: - post: - summary: Perform a selection operation on a dataset. - description: With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. - - requestBody: - description: GraphQL Query - content: - text/plain: - schema: - type: string - example: "{hospital_b_patient_results {table_1 {sex, age}}" - required: true - - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - application/gzip: - schema: - type: object - x-content-type: application/gzip - 400: - description: Bad Request (400) - The server cannot process the request. Please check your request body. - 401: - description: Unauthorized (401) - 404: - description: Not Found (404) - Dataset not found. - - tags: - - Selection API - - - /selection/preview: - post: - summary: Preview the results of a selection operation on a dataset. - description: With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. - - requestBody: - description: GraphQL Query - content: - text/plain: - schema: - type: string - example: "{hospital_b_patient_results {table_1 {sex, age}}" - required: true - - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - application/gzip: - schema: - type: object - x-content-type: application/gzip - 400: - description: Bad Request (400) - The server cannot process the request. Please check your request body. - 401: - description: Unauthorized (401) - 404: - description: Not Found (404) - Dataset not found. - - tags: - - Selection API - - - /selection/profile: - post: - summary: Get a profile of a selection operation on a dataset. - description: Returns a set of metrics for the given selection operation. - - requestBody: - description: GraphQL Query - content: - text/plain: - schema: - type: string - example: "{hospital_b_patient_results {table_1 {sex, age}}" - required: true - - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - 400: - description: Bad Request (400) - The server cannot process the request. Please check your request body. - 401: - description: Unauthorized (401) - 404: - description: Not Found (404) - Dataset not found. - - tags: - - Selection API - - - /health_check: - get: - summary: Get a health check of the service. - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/health_check" - - tags: - - Health Check - - -components: - schemas: - dataset_list: - description: A list of dataset summaries. - required: - - datasets - type: object - properties: - datasets: - type: array - items: - $ref: "#/components/schemas/dataset_summary" - - example: - datasets: - - id: "hospital_a_patient_results" - dataset_name: "Hospital A Patient Results" - author: "John Smith" - - id: "medical_trial_b_results" - dataset_name: "Medical Trial B Results" - author: "Jane Jones" - - dataset_summary: - description: A summary of a dataset, including the dataset ID, the dataset name, and the author of the dataset. - required: - - id - type: object - properties: - id: - type: string - dataset_name: - type: string - author: - type: string - - example: - id: "hospital_a_patient_results" - dataset_name: "Hospital A Patient Results" - author: "John Smith" - - dataset_information: - description: A combined object of metadata from the catalogue entry and the dictionary field descriptions. - required: - - catalogue - - dictionaries - type: object - properties: - catalogue: - $ref: "#/components/schemas/DCAT_metadata" - dictionaries: - type: array - items: - $ref: "#/components/schemas/data_dictionary" - - DCAT_metadata: - description: A catalogue of the dataset metadata defined to the DCAT specifications. - required: - - id - type: object - properties: - id: - type: string - title: - type: string - description: - type: string - creator: - type: string - contactPoint: - type: string - publisher: - $ref: "#/components/schemas/DCAT_metadata_publisher" - license: - type: string - versionInfo: - type: string - additionalProperties: - type: object - - example: - id: "medical_trial_b_results" - title: "Medical Trail B Results" - description: "A description of the example medical trial and the data contained in this example." - creator: "Jane Jones" - contactPoint: "jane.jones@example.com" - publisher: - name: "Example Medical Trial Org" - url: "www.examplemedicaltrial.org" - license: "https://creativecommons.org/licenses/by/3.0/" - versionInfo: "1.0" - additionalProperties: {additional_tags: ["tag1", "tag2", "tag3"], extra_info: "Some additional information."} - - - DCAT_metadata_publisher: - description: An object containing the publisher name and url for the DCAT catalogue. - required: - - name - - url - type: object - properties: - name: - type: string - url: - type: string - - example: - name: "Example Medical Trial Org" - url: "www.examplemedicaltrial.org" - - data_dictionary_list: - description: A list of dataset dictionaries for a particular dataset. - type: object - properties: - dictionaries: - type: array - items: - $ref: "#/components/schemas/data_dictionary" - - example: - dictionaries: - - id: "table_1" - fields: - - name: "sex" - label: "Sex" - type: "text" - description: "Sex of patient, factor with levels (F or M)" - constraints: "SEX" - - name: "age_brc" - label: "Age" - type: "text" - description: "Age group, of the forms 18-25 (B1), 25-50 (B2), 50+ (B3)." - constraints: "AGE" - lookups: { - AGE: [{name: "B1", description: "Bracket 1 - 18-25"}, - {name: "B2", description: "Bracket 2 - 25-50"}, - {name: "B3", description: "Bracket 3 - 50+"}], - SEX: [{name: "F", description: "female"}, {name: "M", description: "male"}]} - - id: "table_2" - fields: - - name: "sex" - label: "Sex" - type: "text" - description: "Sex of patient, factor with levels (F or M)" - constraints: "SEX" - - name: "age_brc" - label: "Age" - type: "text" - description: "Age group, of the forms 18-25 (B1), 25-50 (B2), 50+ (B3)." - constraints: "AGE" - lookups: {AGE: [{name: "B1", description: "Bracket 1 - 18-25"}, - {name: "B2", description: "Bracket 2 - 25-50"}, - {name: "B3", description: "Bracket 3 - 50+"}], - SEX: [{name: "F", description: "female"}, {name: "M", description: "male"}]} - - data_dictionary: - description: An object containing the list of fields and lookups for a particular dataset table. - required: - - id - type: object - properties: - id: - type: string - fields: - type: array - items: - $ref: "#/components/schemas/data_dictionary_field" - lookups: - type: object - additionalProperties: - $ref: "#/components/schemas/data_dictionary_lookup" - - example: - id: "table_1" - fields: - - name: "sex" - label: "Sex" - type: "text" - description: "Sex of patient, factor with levels (F or M)" - constraints: "SEX" - - name: "age_brc" - label: "Age" - type: "text" - description: "Age group, of the forms 18-25 (B1), 25-50 (B2), 50+ (B3)." - lookups: {AGE: [{name: "B1", description: "Bracket 1 - 18-25"}, - {name: "B2", description: "Bracket 2 - 25-50"}, - {name: "B3", description: "Bracket 3 - 50+"}], - SEX: [{name: "F", description: "female"}, {name: "M", description: "male"}]} - - data_dictionary_field: - description: A description of a field entry in the dataset table. - required: - - name - - label - - type - type: object - properties: - name: - type: string - label: - type: string - type: - type: string - description: - type: string - constraints: - type: string - - example: - name: "sex" - label: "Sex" - type: "text" - description: "Sex of patient, factor with levels (F or M)" - constraints: "SEX" - - data_dictionary_lookup: - description: A list of lookups for field entries in the dataset dictionary. The items can be arbitrarily named but follow a defined structure. - type: array - items: - $ref: "#/components/schemas/data_dictionary_lookup_inner" - - data_dictionary_lookup_inner: - description: A lookup reference for field entries in the dataset dictionary. - required: - - name - - description - type: object - properties: - name: - type: string - description: - type: string - - example: - name: "F" - description: "female" - - selection_success: - description: A object containing a boolean response for whether an action was successful or not. - type: object - properties: - success: - type: boolean - example: - success: true - - health_check: - description: A response - type: object - properties: - version: - type: string - health_check: - type: boolean - example: - version: "1.1.0" - health_check: true - - - securitySchemes: - oAuth2Implicit: - type: oauth2 - flows: - implicit: - authorizationUrl: https://example.org/api/authorize - scopes: - api://example_id/read_api: allows reading resources - x-tokenInfoFunc: common_api_server.controllers.authorization_controller.check_oAuth2Implicit - x-scopeValidateFunc: common_api_server.controllers.authorization_controller.validate_scope_oAuth2Implicit diff --git a/api/task_execution.openapi.yaml b/api/task_execution.openapi.yaml deleted file mode 100644 index 357fd07..0000000 --- a/api/task_execution.openapi.yaml +++ /dev/null @@ -1,654 +0,0 @@ -openapi: 3.0.0 -info: - title: Task Execution Service - version: "0.4.0-fds-patch" - contact: - name: Susheel Varma - email: susheel.varma@hdruk.ac.uk - license: - name: MIT - description: GA4GH Task Execution Service (with FDS extenstions) -tags: - - Task Service - - name: TaskService -paths: - /tasks: - get: - summary: List Tasks - operationId: ListTasks - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/tesListTasksResponse' - parameters: - - name: name_prefix - description: |- - OPTIONAL. Filter the list to include tasks where the name matches this prefix. - If unspecified, no task name filtering is done. - in: query - required: false - schema: - type: string - - name: page_size - description: |- - OPTIONAL. Number of tasks to return in one page. - Must be less than 2048. Defaults to 256. - in: query - required: false - schema: - type: integer - format: int64 - - name: page_token - description: |- - OPTIONAL. Page token is used to retrieve the next page of results. - If unspecified, returns the first page of results. - See ListTasksResponse.next_page_token - in: query - required: false - schema: - type: string - - name: view - description: |- - OPTIONAL. Affects the fields included in the returned Task messages. - See TaskView below. - - - MINIMAL: Task message will include ONLY the fields: - Task.Id - Task.State - - BASIC: Task message will include all fields EXCEPT: - Task.ExecutorLog.stdout - Task.ExecutorLog.stderr - Input.content - TaskLog.system_logs - - FULL: Task message includes all fields. - in: query - required: false - schema: - type: string - enum: - - MINIMAL - - BASIC - - FULL - default: MINIMAL - description: 'List tasks.TaskView is requested as such: "v1/tasks?view=BASIC"' - tags: - - TaskService - post: - summary: Create a new task. - operationId: CreateTask - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/tesCreateTaskResponse' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/tesTask' - required: true - tags: - - TaskService - description: Create a new task. - /tasks/service-info: - get: - summary: Service Info - operationId: GetServiceInfo - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/tesServiceInfo' - tags: - - TaskService - description: 'GetServiceInfo provides information about the service,such as storage details, resource availability, and other documentation.' - '/tasks/{id}': - get: - summary: Get a task. - operationId: GetTask - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/tesTask' - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: view - description: |- - OPTIONAL. Affects the fields included in the returned Task messages. - See TaskView below. - - - MINIMAL: Task message will include ONLY the fields: - Task.Id - Task.State - - BASIC: Task message will include all fields EXCEPT: - Task.ExecutorLog.stdout - Task.ExecutorLog.stderr - Input.content - TaskLog.system_logs - - FULL: Task message includes all fields. - in: query - required: false - schema: - type: string - enum: - - MINIMAL - - BASIC - - FULL - default: MINIMAL - tags: - - TaskService - description: 'Get a task. TaskView is requested as such: "v1/tasks/{id}?view=FULL"' - '/tasks/{id}/cancel': - post: - summary: Cancel a task. - operationId: CancelTask - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/tesCancelTaskResponse' - tags: - - TaskService - description: Cancel a task. - parameters: - - schema: - type: string - name: id - in: path - required: true - description: Task ID - /tasks/validate: - post: - summary: Validate a Task - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/tesValidateTaskResponse' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/tesCancelTaskResponse' - operationId: ValidateTask - requestBody: - content: - application/json: - schema: - type: object - properties: {} - application/xml: - schema: - $ref: '#/components/schemas/tesTask' - description: Validate a Task -servers: - - url: /ga4gh/tes/v1 -components: - schemas: - tesCancelTaskResponse: - type: object - description: CancelTaskResponse describes a response from the CancelTask endpoint. - readOnly: true - tesCreateTaskResponse: - type: object - properties: - id: - type: string - description: Task identifier assigned by the server. - description: CreateTaskResponse describes a response from the CreateTask endpoint. - readOnly: true - required: - - id - tesExecutor: - type: object - properties: - image: - type: string - description: |- - Name of the container image, for example: - ubuntu - quay.io/aptible/ubuntu - gcr.io/my-org/my-image - etc... - command: - type: array - items: - type: string - description: |- - A sequence of program arguments to execute, where the first argument - is the program to execute (i.e. argv). - workdir: - type: string - description: |- - The working directory that the command will be executed in. - Defaults to the directory set by the container image. - stdin: - type: string - description: |- - Path inside the container to a file which will be piped - to the executor's stdin. Must be an absolute path. - stdout: - type: string - description: |- - Path inside the container to a file where the executor's - stdout will be written to. Must be an absolute path. - stderr: - type: string - description: |- - Path inside the container to a file where the executor's - stderr will be written to. Must be an absolute path. - env: - type: object - additionalProperties: - type: string - description: Enviromental variables to set within the container. - description: 'Executor describes a command to be executed, and its environment.' - required: - - image - - command - tesExecutorLog: - type: object - properties: - start_time: - type: string - description: 'Time the executor started, in RFC 3339 format.' - end_time: - type: string - description: 'Time the executor ended, in RFC 3339 format.' - stdout: - type: string - description: |- - Stdout content. - - This is meant for convenience. No guarantees are made about the content. - Implementations may chose different approaches: only the head, only the tail, - a URL reference only, etc. - - In order to capture the full stdout users should set Executor.stdout - to a container file path, and use Task.outputs to upload that file - to permanent storage. - stderr: - type: string - description: |- - Stderr content. - - This is meant for convenience. No guarantees are made about the content. - Implementations may chose different approaches: only the head, only the tail, - a URL reference only, etc. - - In order to capture the full stderr users should set Executor.stderr - to a container file path, and use Task.outputs to upload that file - to permanent storage. - exit_code: - type: integer - format: int32 - description: Exit code. - description: ExecutorLog describes logging information related to an Executor. - required: - - exit_code - readOnly: true - tesFileType: - type: string - enum: - - FILE - - DIRECTORY - default: FILE - tesInput: - type: object - properties: - name: - type: string - description: - type: string - url: - type: string - description: |- - REQUIRED, unless "content" is set. - - URL in long term storage, for example: - s3://my-object-store/file1 - gs://my-bucket/file2 - file:///path/to/my/file - /path/to/my/file - etc... - path: - type: string - description: |- - Path of the file inside the container. - Must be an absolute path. - type: - $ref: '#/components/schemas/tesFileType' - content: - type: string - description: |- - File content literal. - Implementations should support a minimum of 128 KiB in this field and may define its own maximum. - UTF-8 encoded - - If content is not empty, "url" must be ignored. - description: Input describes Task input files. - required: - - type - - path - tesListTasksResponse: - type: object - properties: - tasks: - type: array - items: - $ref: '#/components/schemas/tesTask' - description: List of tasks. - next_page_token: - type: string - description: |- - Token used to return the next page of results. - See TaskListRequest.next_page_token - description: ListTasksResponse describes a response from the ListTasks endpoint. - required: - - tasks - readOnly: true - tesOutput: - type: object - properties: - name: - type: string - description: - type: string - url: - type: string - description: |- - URL in long term storage, for example: - s3://my-object-store/file1 - gs://my-bucket/file2 - file:///path/to/my/file - /path/to/my/file - etc... - path: - type: string - description: |- - Path of the file inside the container. - Must be an absolute path. - type: - $ref: '#/components/schemas/tesFileType' - description: Output describes Task output files. - required: - - url - - path - - type - tesOutputFileLog: - type: object - properties: - url: - type: string - description: 'URL of the file in storage, e.g. s3://bucket/file.txt' - path: - type: string - description: Path of the file inside the container. Must be an absolute path. - size_bytes: - type: string - format: int64 - description: Size of the file in bytes. - description: |- - OutputFileLog describes a single output file. This describes - file details after the task has completed successfully, - for logging purposes. - readOnly: true - required: - - url - - path - - size_bytes - tesResources: - type: object - properties: - cpu_cores: - type: integer - format: int64 - description: Requested number of CPUs - preemptible: - type: boolean - format: boolean - description: Is the task allowed to run on preemptible compute instances (e.g. AWS Spot)? - ram_gb: - type: number - format: double - description: Requested RAM required in gigabytes (GB) - disk_gb: - type: number - format: double - description: Requested disk size in gigabytes (GB) - zones: - type: array - items: - type: string - description: Request that the task be run in these compute zones. - description: Resources describes the resources requested by a task. - tesServiceInfo: - type: object - description: |- - ServiceInfo describes information about the service, - such as storage details, resource availability, - and other documentation. - readOnly: true - properties: - name: - type: string - description: 'Returns the name of the service, e.g. "ohsu-compbio-funnel".' - doc: - type: string - description: 'Returns a documentation string, e.g. "Hey, we''re OHSU Comp. Bio!".' - storage: - type: array - description: |- - Lists some, but not necessarily all, storage locations supported by the service. - - Must be in a valid URL format. - e.g. - file:///path/to/local/funnel-storage - s3://ohsu-compbio-funnel/storage - etc. - items: - type: string - registries: - type: - - string - - array - items: - type: object - properties: - registry_id: - type: string - registry_name: - type: string - registry_host: - type: string - registry_url: - type: string - tesState: - type: string - enum: - - UNKNOWN - - QUEUED - - INITIALIZING - - RUNNING - - PAUSED - - COMPLETE - - EXECUTOR_ERROR - - SYSTEM_ERROR - - CANCELED - default: UNKNOWN - description: |- - Task states. - - - UNKNOWN: The state of the task is unknown. - - This provides a safe default for messages where this field is missing, - for example, so that a missing field does not accidentally imply that - the state is QUEUED. - - QUEUED: The task is queued. - - INITIALIZING: The task has been assigned to a worker and is currently preparing to run. - For example, the worker may be turning on, downloading input files, etc. - - RUNNING: The task is running. Input files are downloaded and the first Executor - has been started. - - PAUSED: The task is paused. - - An implementation may have the ability to pause a task, but this is not required. - - COMPLETE: The task has completed running. Executors have exited without error - and output files have been successfully uploaded. - - EXECUTOR_ERROR: The task encountered an error in one of the Executor processes. Generally, - this means that an Executor exited with a non-zero exit code. - - SYSTEM_ERROR: The task was stopped due to a system error, but not from an Executor, - for example an upload failed due to network issues, the worker's ran out - of disk space, etc. - - CANCELED: The task was canceled by the user. - readOnly: true - tesTask: - type: object - properties: - id: - type: string - description: Task identifier assigned by the server. - readOnly: true - state: - $ref: '#/components/schemas/tesState' - name: - type: string - description: - type: string - inputs: - type: array - items: - $ref: '#/components/schemas/tesInput' - description: |- - Input files. - Inputs will be downloaded and mounted into the executor container. - outputs: - type: array - items: - $ref: '#/components/schemas/tesOutput' - description: |- - Output files. - Outputs will be uploaded from the executor container to long-term storage. - resources: - $ref: '#/components/schemas/tesResources' - executors: - type: array - items: - $ref: '#/components/schemas/tesExecutor' - description: |- - A list of executors to be run, sequentially. Execution stops - on the first error. - volumes: - type: array - items: - type: string - description: |- - Volumes are directories which may be used to share data between - Executors. Volumes are initialized as empty directories by the - system when the task starts and are mounted at the same path - in each Executor. - - For example, given a volume defined at "/vol/A", - executor 1 may write a file to "/vol/A/exec1.out.txt", then - executor 2 may read from that file. - - (Essentially, this translates to a `docker run -v` flag where - the container path is the same for each executor). - tags: - type: object - additionalProperties: - type: string - description: A key-value map of arbitrary tags. - logs: - type: array - items: - $ref: '#/components/schemas/tesTaskLog' - description: |- - Task logging information. - Normally, this will contain only one entry, but in the case where - a task fails and is retried, an entry will be appended to this list. - readOnly: true - creation_time: - type: string - description: |- - Date + time the task was created, in RFC 3339 format. - This is set by the system, not the client. - readOnly: true - description: Task describes an instance of a task. - required: - - executors - tesTaskLog: - type: object - properties: - logs: - type: array - items: - $ref: '#/components/schemas/tesExecutorLog' - description: Logs for each executor - metadata: - type: object - additionalProperties: - type: string - description: Arbitrary logging metadata included by the implementation. - start_time: - type: string - description: 'When the task started, in RFC 3339 format.' - end_time: - type: string - description: 'When the task ended, in RFC 3339 format.' - outputs: - type: array - items: - $ref: '#/components/schemas/tesOutputFileLog' - description: |- - Information about all output files. Directory outputs are - flattened into separate items. - system_logs: - type: array - items: - type: string - description: |- - System logs are any logs the system decides are relevant, - which are not tied directly to an Executor process. - Content is implementation specific: format, size, etc. - - System logs may be collected here to provide convenient access. - - For example, the system may include the name of the host - where the task is executing, an error message that caused - a SYSTEM_ERROR state (e.g. disk is full), etc. - - System logs are only included in the FULL task view. - description: TaskLog describes logging information related to a Task. - required: - - logs - - outputs - readOnly: true - tesValidateTaskResponse: - title: tesValidateTaskResponse - type: object - properties: - success: - type: string - enum: - - 'true' - - 'false' diff --git a/common-api-metadata b/common-api-metadata new file mode 160000 index 0000000..3e04840 --- /dev/null +++ b/common-api-metadata @@ -0,0 +1 @@ +Subproject commit 3e048401dbe540d800567bc8647f3ebf327872b2 diff --git a/common-api-selection b/common-api-selection new file mode 160000 index 0000000..58dae61 --- /dev/null +++ b/common-api-selection @@ -0,0 +1 @@ +Subproject commit 58dae619044c61d8485b619e98d8ab055ff19c7d diff --git a/common-api-tasks b/common-api-tasks new file mode 160000 index 0000000..a57a3d4 --- /dev/null +++ b/common-api-tasks @@ -0,0 +1 @@ +Subproject commit a57a3d437312a39272c0f2838d02498dc0b11586 From ad28cbcf8a1c2c3207023e77d404b13f39df042b Mon Sep 17 00:00:00 2001 From: sblowers-aridhia Date: Fri, 3 Jul 2020 10:24:32 +0100 Subject: [PATCH 02/51] update submodules --- common-api-selection | 2 +- common-api-tasks | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common-api-selection b/common-api-selection index 58dae61..76f9c0a 160000 --- a/common-api-selection +++ b/common-api-selection @@ -1 +1 @@ -Subproject commit 58dae619044c61d8485b619e98d8ab055ff19c7d +Subproject commit 76f9c0a689bcea7620e9fb86548defc70d40a956 diff --git a/common-api-tasks b/common-api-tasks index a57a3d4..61d542e 160000 --- a/common-api-tasks +++ b/common-api-tasks @@ -1 +1 @@ -Subproject commit a57a3d437312a39272c0f2838d02498dc0b11586 +Subproject commit 61d542e69274fc9057bec7ec505b539af4f64a2b From 3f7e6416752861a1931401f768a044a3e8fb3902 Mon Sep 17 00:00:00 2001 From: sblowers-aridhia Date: Fri, 3 Jul 2020 10:57:16 +0100 Subject: [PATCH 03/51] updated submodules --- common-api-metadata | 2 +- common-api-selection | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common-api-metadata b/common-api-metadata index 3e04840..7fd106b 160000 --- a/common-api-metadata +++ b/common-api-metadata @@ -1 +1 @@ -Subproject commit 3e048401dbe540d800567bc8647f3ebf327872b2 +Subproject commit 7fd106bd96f285e172b623635d0ce27e34b890b9 diff --git a/common-api-selection b/common-api-selection index 76f9c0a..c0a234e 160000 --- a/common-api-selection +++ b/common-api-selection @@ -1 +1 @@ -Subproject commit 76f9c0a689bcea7620e9fb86548defc70d40a956 +Subproject commit c0a234e45836d07d4385d0a88a7999e2dde12621 From fe8b8457d37e63547c0030e8f69a02bbd6acf693 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 12:11:09 +0100 Subject: [PATCH 04/51] Updating documentation --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6be2758..7e99079 100755 --- a/README.md +++ b/README.md @@ -4,19 +4,29 @@ This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The API was original developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data repositories. -The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact [Aridhia Informatics](https://www.aridhia.com/contact-our-team/) for more information. +## Contributing + +The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). + +As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact [Aridhia Informatics](https://www.aridhia.com/contact-our-team/) for more information. ## API overview -The federated data sharing API provides a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. There are three sections to the API: +The federated data sharing API provides a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. Features: + +- The API is defined Open API specifications. +- API endpoints should be authenticated using OAuth tokens (out of band for this version) +- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying -- Metadata -- Selection -- Federated compute +There are three sections to the API: -Note that the federated compute API section is being reviewed and will be added shortly. +| Section | Repository | +|:------------------|:--------------------------------------------------------------------------------------| +|Metadata |[common-api-metadata](https://github.com/federated-data-sharing/common-api-metadata) | +|Selection |[common-api-selection](https://github.com/federated-data-sharing/common-api-selection) | +|Federated compute |[common-api-tasks](https://github.com/federated-data-sharing/common-api-tasks) | -The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). +For maximum flexibility each section of the Common API is defined in separate submodules and repositories. In this way, sites can implement combinations as required or desirable in their particular setting.The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). | Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | |:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| @@ -24,12 +34,6 @@ The table below illustrates how different sections of the API could be opened up | Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | | Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | -Features: - -- The API is defined in an [Open API specification](api/common_api.yml) -- API endpoints should be authenticated using OAuth tokens (out of band for this version) -- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying - Details of each endpoint: |Endpoint |HTTP |Summary | From 9512b6c3213b08ebbb1c5247265a96990436e914 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 12:47:13 +0100 Subject: [PATCH 05/51] Assume ssh not https for submodules --- .gitmodules | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index f2f13dd..26e41af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "common-api-metadata"] path = common-api-metadata - url = https://github.com/federated-data-sharing/common-api-metadata + url = git@github.com:federated-data-sharing/common-api-metadata.git [submodule "common-api-selection"] path = common-api-selection - url = https://github.com/federated-data-sharing/common-api-selection + url = git@github.com:federated-data-sharing/common-api-selection.git [submodule "common-api-tasks"] path = common-api-tasks - url = https://github.com/federated-data-sharing/common-api-tasks + url = git@github.com:federated-data-sharing/common-api-tasks.git From 977652df7980d8e409530bfc5db68795b9c56575 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 12:52:28 +0100 Subject: [PATCH 06/51] Updated dependencies to submodules --- common-api-metadata | 2 +- common-api-selection | 2 +- common-api-tasks | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common-api-metadata b/common-api-metadata index 7fd106b..55e3309 160000 --- a/common-api-metadata +++ b/common-api-metadata @@ -1 +1 @@ -Subproject commit 7fd106bd96f285e172b623635d0ce27e34b890b9 +Subproject commit 55e33092a97779e11d90fedb5736a86917517472 diff --git a/common-api-selection b/common-api-selection index c0a234e..315dda3 160000 --- a/common-api-selection +++ b/common-api-selection @@ -1 +1 @@ -Subproject commit c0a234e45836d07d4385d0a88a7999e2dde12621 +Subproject commit 315dda3d8b213b4bb2f3d46d69039a2a73dde41d diff --git a/common-api-tasks b/common-api-tasks index 61d542e..2cf3d21 160000 --- a/common-api-tasks +++ b/common-api-tasks @@ -1 +1 @@ -Subproject commit 61d542e69274fc9057bec7ec505b539af4f64a2b +Subproject commit 2cf3d219a965ce40b6e110ffca895ab470c76c5a From cd305a175ba9ee38da35b37057b9d26e906c7d1c Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 12:55:24 +0100 Subject: [PATCH 07/51] Added Origins file --- doc/FDS_Strawman_Architecture_Sketch.png | Bin 0 -> 91432 bytes doc/Origins.md | 35 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100755 doc/FDS_Strawman_Architecture_Sketch.png create mode 100755 doc/Origins.md diff --git a/doc/FDS_Strawman_Architecture_Sketch.png b/doc/FDS_Strawman_Architecture_Sketch.png new file mode 100755 index 0000000000000000000000000000000000000000..b8033056229c97b941fe3ad6214ff67a3d0b9505 GIT binary patch literal 91432 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�JHO&h-0qits(NAXc)V7HO$9g^G$5j_n z-r!o6CpJgLqTdvnjY$+DENBGZ)ak=XH|?LNb$m6NxosJ%NMtKMJ{^)ISUdVz^&Uaisp4p zk=eLbyR9bNe}7iy;_ReKLU7Q@K<4CJp&3KNPa>^Ip^&@ka{kQJQqKR$`#VX! zFLO(>ARBb-7C*Y*oH)7+KYN*ZtnRW5Osgc;jMRMDB1PID^*pL{(&YKQhAwPrOd90b zXle37Chb#?r%|~~n(Ni3ixW~mLW{$7K|7L?tnCa#?}l&B9&$8l9ryT#PJbSQ$}9(8 z*#C}feWlj&<#Rb@*AFF`@>)@fg54|>1_67exQF3>5|_CHp3 zf~hw~PI~q8-m{VprJi1&XhGb%JC8a}l*s$6V=12@1la0slhN4u@A)m1E{+WTa@FRP z>V@bzb>r__l991!gIiWC0^D$;39(a}3!qR>?-~^?AV)(3i{yklNUqbE?}l5>wG0D2GtpHd7G1D|_;X8%ccMyF8k;p6)!Z zZ=OxEzXhG8r6ay+jGYJHilo#bstTM4mfllgg1$H5hilJZq>-|wZZ4@a`2BuxubTM5A%&O7DGuc!XgFFTM!=G(!jpDyKlQ1njb zP8nC`Psb4Jovd1lbX`^bl~#HlD&YP~rDSMg8B=tn{fN|0kSXyUn-GHoEHFj8(?HLn zza0~}zRBa9DL3+$+7aW&>WneW9)*)B-(FT+&%jB3&&zM4`vKwI2myUGE}X_@wXt2) z*}!AkC3_ggrqOo7oSiTupVnKccnPA9Q*9TU>>U-VJiPu=bM;WL z7BE?ExoL=0L8qIgl^u2LNBAa2OyD!gi*ct8a52fJ_-MntS9iIbGRriNDbaZPG&_CZVN((!bv=(lD>-9%bWTFU8pT6$ z?It6CqMxB!fGQTlVe%`h`w%K)pTKPRAZvLHY};}ozThUu74Pnzts$8&0{UO>dAXJME%L*e6GDkT1-ihR3;w8@zAkERZ zIT4|zkaj0qTC%W}OLfrPXwPFx~L&u`@0it z9hOX8ogz*7!+!61X%`c>2*GYRl268Da5hp|Z7O&$bj?)s^B_5EV^rrh(td6wP+q~j z`y{vGO?YuKgzF%qC9?1f4 z7$%m9xlq}hDf<)Oo{c{AsxO^Ewo77wdOK_Qx*b~@p@qWi^8DAo8xZlfJxPr^r5P5d zcvbejhCv7dgm%G2oQ1%C-__MWZP}kJ&8=&amSeb!Q9dd$XX`?w*Bih}nH*eAEt5Sf zw%#udYZrP>ekXVQZ!3N1bZVi=-&=)oKtZ`kO{aY-e;d56Q+Z^yr?m^8ks2jtrdW_q z)EV<73-vh6A8ffvc;lw(lW0T3#Ccoyd!==UDZ+WWVVbFqgw>zJtv7I@);ZJ!U=cy{P+_zs6WBkmj3 zSu+rvjb=t&k_Ly{ytqLrE_nmat~Z#dHl)}m$t11lBNL<%%0XZE~zI2NVl=wk{LZnkj+_e2#DF>ylg zsj-4JjmD-))?ipu!7O4n!sIHK&%@Fm`_DN$xEf%>X)_-LE+CA3aA;bDK79U%4!X5N zesNq~lSnFEgHxPdr>`;kzIlUYh_+4JFf&H9Pu#9*57II1om^Fwpyx%_$fBc0n?a#6~|J5_q4fSop@I;E*y<~nq))@YB^47p=(}SU?bxVwu7%~!@4X!55IHoAtmIJCaJDcB z7KtjSd_JGIZtR;1A#n6)^$@tLH+iK$63xdy%6w0IlNabuZRb!g#W!|%2}rZzODB6S3j*c>uS^?@FlD^%4%v_nFA7^0Et z8Z5GP(}_@*ho_(3oR`lkC_BrMQlMXKNDt&q9Iw@BZbld+1oAhs3DLT_n&HM`5%hyK zFT!arCz3k>v?)kK!_IaS;+^OPtVLd7@wd$?_cEGR9VH`Hz_T zu1|PeInSu#9tm;~fD;nH!m8?=q<(sB6I}KTg^Xe=p~t2opO+zDrov$ogyX23|3awK znB`<)uIHp?C-Mk2?INUirt4VyuiWde1CnWHHP1PGfRVamqEVV7yk;k|!KqBut(;$c z&nina?8_Z>3K395Y&Avqi1_2bG>;;JD3X;K zm(k@Wa~3h?ytUrrj?0jJDm88msNlE6LOGQX!H6!ZJUjDul9P>a>$1#i+7?UUHAhRb zt+&+mTMZu3q~ssM6ctu3cJ)&{1?WW3T_bQaA(j~k%k9EQj!Ykx5272$T5KMJeQP8q zt1F$&ViBEFTajW-%@fIQ9%7Ln_X)}F*{P~SUmYb9E%gNSQm4oo&^!V4)U^T5Gj3tY z^usDi=I%A1wA=ZC$Y^}@wdRKvnfuW;{z)DX6wRzsxo$37Fr*qkBi*Rlh1pU8O=#cpEILdZMf#Y70ZXejltxS&>RA4U!_ zEotH@k9jseU4)Rq*9Xh6%%R%ZxIDchN`}4dyX(eUH3;G)UojSG32MDYaM!hU6y3qy zc$LezBADEMw_woxMzl^$ao~6mh615JSU#pSg=~&yTr?byl!SUYmn2Uy0he9k%J-ip z2X-0^G^_o&`YxIie4K}Y&_faXV>!R9S(9twT$gLM-Pbe<`&j`QZIztQMq(Egz%+~V?FZmc?lV95b#Xga!_1F>j&46 zkBT+_u+K!IGRh*LsJGU8cG{kP$Ha6+A)x0aeT+q5w#KL^^gxK)cH4c*udd9GP7ku0s8GC&cPQ%kz-5L*vTfNUhu6@)FnB*J3B{ zxE?hi_g<!re#+Tl7?nSxah2|q5c z{`9p||NLUn-?hBI5I3r~2C3g(i1hbmW<{)>5N--+WyHIEFM8c;V!2oMhp8DwP$b!N zd60e_-S;*|h~KEeZCEh>!u1;2JWk~1HnpF>4SUB`xTD|iSPTCx#sc?80s~>R%e_k9 z!EMzo`ttPeB=E+k>gUsP-$2~!_PMi#mi*lj!OhDA{5+_`Gp=!$(Xr{iHe!@)BFrUlC z@y1TeT_%JYh6|C6QDao*2zO4Tb@i8|&aTt-3lT z?7Ll)T}24PN4{VAN!QT3)ZLqDnI z@Q3H2dck9lG`k0eb*n-l5VsI(V)gx7hd$e?O1b^9lTZBi+p?ml7Qb7j2zg3nf)tV# zbBIu0rE)a&yHR`P0&h5zfG8}J8oZu_Eo4p>()oqn@ZKj_RnO0FEmgcMZx#Nd>mCy8 z=OFXSoc_h~+l^GQf{^e}aw87;8D~bc?8w7E2i#oO^(3E#K$P7h1eDw!4+Qt~9{m*7 ze^sJOLiM1uzkxQmC;0LB2EGCts@vpCw4&sa*2)d)N;@>%tk&9MrQeR|;0p`_z*lZ!i&0%S6Do zK%YT~oRNyizO#CKInQB?6DO>%lzHV>6A9PQy;aEz>YM*L|NRIVq2~{=nUYrTXp60H;k-&F zRDc=$v2eN6cR96wcFAc6&~o)t zb?#n{xn*0H(}z+UQ&*<@Xsi6R%{khL{&MzQgYJGN3FATDrTO*#?+xue@Us`^6`B*~ zw(=zLvy`$?4R59LO;(gfx%w6I&{QTlumdIseJ`;iWWvWsGg43Arwms=uZs#v)KWi} z5@#&{6>V;#OwZcm>pa{0W4t)F>^Q2ZFbl+-&yrjBGe}U!4IWbZGqd_aFJO{PDV24f z!QzdYd&0r$Ln65dnwvrIFG6u4B*Bp{Y-EAFPs8&7;p?=MiIs3?lSqMXrf|$V2p=!8DpU@lHfQrtq&ytRR zH?2esyUN^e@t^8zylY4G^vK+EX1Ccm(^UI5Hh;vp#Wcv8ssn@m;Ss#$K_P|mT9PJ+ z^&4${2iH4pIAEG7un>?9QlF|75eNiSyA_1u$4-_%?sAHTmj@cgxabZ`Q&|0n_q?{D z9xkx!2=eeCYlR9JxQwLv=eBxTq2B$L2d1J=miwRb-{p@|O7+lsP~*N%%Wby&c{xEJ zYOf6yLr}Q}M18vB!@|UDhv5VDw(+!AatsaNTAYtxULi`4L#Q4yw@gwVKA5>LA4qx9 zyDYl;qsy{HINNCdF62=J=SNrFY;xOc+Ae|-Zn-peYjF4`byt?a$Xz$(_%JmZa?q#0 z=8pvr8@UFc;Q2c|Io`CvljnpHXm`PUdDvkEwQlcQ?U86f(uklovhsE^Ipv3O--z=u z0|kBQT{pfyXFUd@hO#^C7Qw@8#yzQb{Dd+C4iKq$ftBds7%H8%LjH(BJwYPLM(!lR z^@$9TV%Q~3C7FgrcgDH3T3q|hBfuXWwut z8=j7Br@9(6?^tWaE}tGfv_L!|(xiZmZQkfIkxk?~hx1D^&yjioW#B^2Q+C*$Li9B%iQ))A+$i_~f(p327Y%pRvFGcD}3- zPLSngoP-D~K?a`x8UrF1cz1p`e=1e)Nw4Qk9=1M1Tju- zQMlShvUf1pe0NDhB@%`&6(9*Ef7Nj?F7!n5j8}o?J|PfEjngQQJ#8C>gQ}Arwk>zX z(_qQr{O2jXdJF}P9>Tt7zN}h(@r^(Ax>TAt83l zu;vspNO`o0m-aqTkG7oP0LktIgX$P!xX`so{B1_z@ zXav8|D3OU1CMiekTTvA#{5=eX(OyKOYz2h|2>Tqy9&x)^e6$H$Ts2Upxm`x9iz4Z+ zBFS7{weY{^LO^Hf;YbPY`Fu!n&s+s=($(WN`}8^48z$CG;g_qw1P9r{1PO$5EKp@Z z%NN~qB8nBE**k^|%Pz+w3uHHY3p+@L+ACh2u(0-R^+i}eIdI=kAxTV777qI^%tkry zLs+BK4lPaVj2sl56KPX)YVp;TJEWXzjqX?V=tyKgPGj-elL|+tM{VF*OmVUF~O8k$mgj2NWpM+uevIXg% z=tt1i7hfJ-1JBW4E->)eOsxd*^JxyoJ#7&3VxFvsz3jp%i5Y3M_hARN$oTi_o(H;? zqfOvY#3H3gyP^5IKi0RBzznfk(VJk@+9G3-`rQ6bomXTzh`_vU@c3X#Pzs3q0RkP7q5k zBIX=H)RR(O>84pAHu-I)AUvb9Z>yU|=dCbhe*UY`^C#P?LrEP@>vnczLgb#o2dZE& z3n*5ws78GA))48k=2ZxT;13ah-$7{sLe}LRq6^F0@ltVk8a1c!BzrYmctVZTA@5jV44C5RNpp$`n!W7{1jiki$u zx1*$6Oxw!uloH0BB=G--`YH&QQ9tgI5jUyZHkmcm_Yb?a^(LsHglXV!i;>WZ z1Xv6vRnqemRj=>udevccyc7MpKF}uS*zR#!cSAkqcQ^RSDrh!A2(`VF$asMpe{F4N zHyed=uC(nuu@8y)t4%soc#e51{=|qdh=&?UwD>p!Je^Fqs3CB&AdF>#X5!0Y(m2e= zHxoULZG3xTYCz>{2GG$>mOwPl1Y{e!C|&=q=#27bCi`y|rLB$VoB& z_-z2Bl-B){whX)OznkqrkpyY3wK8IYcn%#PT;7?P(%_8Uy||K1W<9{e5MhXQh#5Vp zJ-kGy9Dd?jip_t@`e8x>BZ!Oh1_{&q`}bjk)}=AmFyG!dU1$VGX^`=zA|a!;5S31sjb*%Y0`f70#q#98{Y#aK)Ax2<0nnq3?#Z5QWX zQ>0M62_bBW@|io|HF+ZrF(WG7q1Es_Da|Dh9w{C=+A58vA;!?z(XN9pig=7$d{s>f zoDVupW=VK)t=YF7ZM~{|L*`4amKa{Sh(?EXE}a?qZP&O2w6U6&lXP>}z{_`<_%I*zL0zp0w8kH@4Xd_V0eGC139c`wZGMSm!XSuk7C-it)X>X|F0tYU7F4 zIWcLb+UtLf=qKPl54aXD$k-8q94>+*pCdyhW7sqNUw%6=U?jEXn+Pc0q9vS>jwhl5 z6>v^U;P9IGJM2IRIMV~JI2?TF2N5th~#HPTn227LAfi(KfWQvFMYAnkmUe&b&IV zQg54O6QkpYH~$wUvw8Uyf|6_#jw-inv)s09@d_|zOYG%W@(mg@XERVJn8L(W0O!-VvIV#?WU zCEdCkPa&qh;bez^9eH_LV$7J6AROsP9!F>xob!U^<(VYWO~n;T8aler;?xXzNczV- z?ZZ$@T+L?om;xr_{?`La@u)&;|KStKj$RYfvtWbK;4Svasq^%pQF`cT_m6jWtvAWB zTDCL0Gb)}po%KC~hl^?DQwaW|iG}qt?WxD5FmBAQ9AS_X5(*{-`B44fZ!D(7WEP6^ z{Q@IWYVV7Dpbg_ebc^Vsa}wr{-3Bk(vIrRVhv`pWo893X&$jkw&F-1a(SKW6VhM{M zXjELR*F==I#S=05%}AiS8}EI3nej9D%s9X}-7+-m8nC*S$#l$cTJ(xiS+rjDq(+M# z2UU&e@Wc#)mOd5E)qS`lw{YKM&&lLrh`cEEuPi_dm63z5r!Qr4EztY3VtEv=A4VwthsSe37 z4Z1axwfP5p^09AiTAOIIU}?dKVzjRQhW5RveB^M@iVowp^7?_Pc>MF~?`U_sZh}Qlx{MlyNS)ocA+D_17Bm}ci z)TMP+k^aPIU!Txh_gFX*>9BQ??ey~j4djM@o|~4@Z!?59k#uU7?~Dj6bCu3 z^*p~k*kMGXaJQZ^orM^o0NZsr);gc0hklJYLs#!QjENmGHN0mw z-#B4$9_N#^Cgw%IXA5Ce*VzB7s$f!3F--fE+zG>_O|TDxjWuT=}~sd01Gt zARRY3BHYPr_Hwdus9yibdwtxY0K(E7??m zgXOFa(OyxLEz19mc!v%(iF~BGTl{Ns$Bsp@! zK{s8S-z)A+4>ll&g65CG?IOOZi2 z$ofj@lFG0D&17EL_QZ?ixww^izhK->qa~Xxww$PhKS8mXVV5!n&CSBd_%o(=)2ccM zccKp&D>Wy2Z5tR+kgUAvm2BXy-ZlmYkR{K#{i;nqo1P|{sOmiO*lUi;hfe;&9HSCv z4xI8dk&qogK`1NIX_9WCk=DewmX5Q;yMbtyk_J23U@ zYt`9qSLQ7SdFE}jnlDf(qTL|tWtJ!s6>HEvHpSTHqUYm}&}L#B=?PI~&iA2;vDIEM zwg{r%0R&87Z?mPy;1{w8aCqFIu}5t?HPXY}Z{R%@MvUtHU?+NaXMDrD!1lqH0MXO? zzml=JLy0v<^fNqNf3aAP)oIQ>Zj_0in#67oX@5)oTs7~08oR@7RJrv2doC&1*Mv;} z>;$KUNz>`hm@92O?dgsQ*m(eo6@L2E>L4cB{byW0ZyUtVifGTpB)!4@cn9EM^xY=N z0ZdLR-+0cqod+kQ=uu;t33@qw%`bCIKxb%fFE6hVRH+aZcau#y1rgLq2V;CAEd=*r zicx+?sUyG2z<)nfd$g%1g5?V_DR80jS0)(A3C76mLBWg)1E=5j*RJwjt26X#pckMrH9^6zji#@$J%G7V3!QTzWAj3i>TPT>PEV;vG&o zrrzNZ-8ff&9di$on3&NCP7ghp3(=ZECu_T3^SYYNg-5Oegwg5Zm5kTsq0^<$hFku{ z$+tH$esbz^^-ul!6#+TWyLgkA&|m2geb#}!CceH zrZL5Pf4`8VzKPjLuw$cmr%~tz#=d|4vA=2JvcjwC*%6$nWqY-BmM1Kw`ct7@pP-Z) zjfH7rOM(go|LP{GdaBdxYwMZ1i7%~^(z)~!+ba2vZEoi(^z8e6<%9?02S&Fr$2*Cj zSRe&pY&Y)D)Uv%MHu+gMmf(J6o?k|@uS?6k-&1OtzOT>|YN}FwO$Y03Vp6qGQQ&Fs z;NM??bg6&~)i^{p2EVhgD*JxswCK6&iKr$#ileZ2Sr%9bS#q->7UaIrB>3rPvq8!_ znGCUyet_k3?5if@km>9d*;sra!?M!)eE)q$kVD*a)FjOVU&nd<1(*VG&iM3ad3C9} zG%`6JM|{|d^4mz85Byk0B}K-2+~ak6^e(;u;L_-(S3rFGb;?|3Q%!yJ6U;!J8Jc>p za(DtA*4`|SSTuQMmcsIaQyGM8-NKL~WYIc%wHwTzCEv3wsoBIIn-RqODkJhL9=o62 z11mD@zrSwpiN9N133Sw~g!n*fyF6UT=oXY#LpS24VPK4JgD+J&#S4mq_!j4*HvN@8*}(c+Zu^=qIT z7<>9;bp&~9>z52dAAk#qT zOxoft%fyhQs7l(c00RHTJp?2A>`xfC0+r#GkxI%@$hmzg@E~78oU7Y&psd{ecPA0E zv5X(tedZdgi^GRPNbrH%yS6poO8OTovRe{jBymBQt`G2BnpaE3>=}b6mcObTrrj)s zAqq5)(qVO08j2S4V;1Q7i8D;L68`f(F`@hm8q?c*68p<-t0c#!e&HecqDzr~ zUtJfa12FW-Cr*nWEv50jQpH-YV(3OkjijN>QPU{%$Ca?iLhCR&w2Qn-TJ$p4YyPbe zJ2dQAPR3&Nj@PrG{AJSJs5(fhF@5Lv116wP!{a~SlOShm>e2}GJus(NH6R(O8_9$S zwIuW=oeT@VoU?^TS+PHkVD7!0o8F+jtO1|upiSWOhcA8;co%X~E|@IKPvXYHxQSW3 zPgmGbD;!{IIFLsKr!jYxeo98T^nqn$N{abKp_`5Ek4JmS%d1gwiqcf31)MhbDW73q z7<)2pNn?4;qB-*-7-hbZWnT5fr~VhoL(RUg-Sh}(7}!gs5b(*SK(8eUIA6}U3zs}t;cg;ol5q&uYs zwwBr+_Witv+C`(F`ISjdyi#Fwa}l%>0VewF zE6-^Ch4dC6L;xrFDi|>Rdjs!Sa}R8Jsp?GJt+qAKy>4a-gmXBc*Ib@ZMa)W=z|6*F zoH5LK;29$&6C83)$|WeApuFH>(REEm-e>y`m-H#orszD~poW~ztIxiNaoHFsGAQOi z?Tg=>wEJ^zfoP!mG|1&S(#F;} zSBUN+A>eULCW1P~Qgq-rTEw!I2}%wGzGReEiXg^htWO##U^~L?ho^CKTYj=%T9zN4 zsye4YqMubKC>e@f)A4-mpE5WM*o#^51-EvS1t6$GK59`Q1g`Y2Ce+Zhd(?}HH>fnM zi|dLiL$D>n5q$EW)(v5{4<&E?^edC~U_v|W0}Wj_A9qf0IFEK_76wVsrg z!`+iFa$~v#4xsKS4d$hFJoTKfo=4hr=yD zgO7*{!R;LhwAbP>ouwN)PzVZ`{avKrQIq@JeqTL7Gzm!TDNlJFmZ!CEae^VxIz~GhP?Io5K_p9o@BR#2hI;<~ z0p-t49CIdhRSnx0m<3cMJlb{mI>QID@1HhQKMttLiQg>bvnu|6#rXw~($?(Qyo7eU z=WidO-#{_6l2e>L@oJ-Mj5jmu{F`tU&%KALfoWj{kMK~-t5oxPJ;Ob~T{8vQ{)uD- zo^Z@-mZy`PSVT6q)n7^+iQ|X;pA(cWqL3NDVBP|X3P8J^qKZCmdi}<HaTBpJ5Xw2$*#w4z} zS5wRy^rPkQejjt&qNPm=zI{(ub?E^bfEc~YTyADwtnv3gVY2G9fCZ$OZ&~GMVc9?0 zox~2zB;MOjY%UOyN~cT~MIpi%1u%>9DF(d}g~`5G6u=(<6g^CEi3{@&ZKwXJ3v3!O z{+D;ofFAb1vc%M+L)0yRLgAlcp@;IH^&C*T1Mct|K0JSpH!t-3QzyrZM@6Muvyc%2 zfIT4k(V-Cp2z<(}_gbZlMVc=F9|N%nwQ?)4(u8rql)}QcXDq=^LGA8 zcH(6sG`+v;Q3qzgr5=#k39o^gH1`0={Lif-out>Msm^fK@@GJwjepijpuGXw`ui zXbV`VPMHF~MvEY9$8}~t3RJ^WOZ+uD@0c&)-56oqNJDkH-~Yq`y#KeWUk!u*N!@|5 z6*5=+AIr|V@UL4%*Som?vlWm5o&o%7bjnXLjj;i(S^u|BM3#y-xd0Si0QyC?PJjmQ z!T6X`42}cA3@~zFpD&Q030}RD3 zZ(Z7O7@=;!hz_t>?anQKyGXaH0nei0yjZNI3!oZp#`jr=OjHj6;Bt!P9)RTDWaJsD z^qb!@_1XqHxa#0-tIe=pS{wg~YJB5D&4v!k^n>WFDZF{w*@ORF@(Ab~ST;KZ$hiYz zzRm$Gu@yk}RzP?6ro`L#qrChA`^47U);m-CI|Bm?GCa=`t@5Eqj2>l9zW@cOK@7sK zAhjRQ?p78av<_4QFr>W9pm*OO0AyQY=NKH$0iUye3=A-`YB%^W0UawUTKZ*To|+6c z;|^B8fYjgm?vJilkD34owwYJiffy7okN$<%Zy$`Ks`=YbEMx|cuA;wQ0_57xvB58d z5BlvI0O1RwW>M9MY%H9^bOvatH`%)7J6-1}qAlsOATHyr&$X z-TD}TZ10Fka-1qlF&Zq&wB?Gqzy+c~CplzGVj$0Avqinq8T$ZkYErYD2b1?|9=I z54+<#$DQ9st;`sMjBC#=TKq-;?b?qv``vWE4T;8e7~|G7P|ipVV{}Nz-dG)#+1oR) zn?)~FkLqdpF_&u00K?p7sY#37740)j@YqYWZ1aIJ&Sv;n#K@eayq3iw{QB2;cJ&I7 z*wo4DUmZ&ZxacjnvStlry5)q#0CYkLBR?w@^Ho!3oMFp%q8!G^V}i3c>l1@A{iw$H zK)uV33VzmFPV7Y2wslxKp#~3q%9M<9uj+xH7;Q$pkG#s9W^UQ4tQJ>Ve$D>hLN=sT zBx%yeUc4>efN^me{H1Ic{k4jS!GZofufx*C*R>6M`1v3KYTaJ=ThwU?dM=rScyDC3 zZUqvBdh@n`k-+*O2EMO3R5TnIkfsk**H_;@m#mlImvT4mtFgj=)pYg$|Efn|fCUE* zBWPKu)3(7LiC+t_mYINO0XVvWG1nOb+}C`bh;_Suu0mcNO;Np6>ohyelFJs|1t25P zK{;JDz&;(Z&|QnqrddigoX($5g<1QV_2nExrSON>c<9`dxN(}+Sw*d8HmJ)>S!W_*` zG17Ze(K~JBm6r6q-n_o+(|dz;zz$mIVQ}zMSg*cf)(7_2jx~UPUnk(1M9Yf#uQ(sD z-$LzLyQlNs-W1yIxV;dTKcBkW80}P_?cry(>CH5o`V{|B)_!}IeucnB57zaq8(hye zQ1_vnc0BvM(SuGfM8i+&Gf8g??A${2a{s}<{|44_Q>@Q>&B3uRdZ+bh3*l|k+!piI z#ikk2ch(}jgWf!nb%}qvEtL)3m9GCO?3WX;Ui@_mow_>}jq`I{XEZ`e0b{a*@K)Hf z+>xq3IMQkno*%Rntd~C4nq43tsONABjNuy!5Vt$lLInW(=wb8V!F^?Z)PiAuCYW!v z{W=MIz>mMf|9=fjFCc)S*^xe7iC+bj#3eGt^S|)`<`vNJH;G#7nYpgmM(#~yA@fys z0_nAGKYXk~KTP0;K`q`jQah9=G)9*HdzkxqID=6?V0EjR`;zbY^%WObTCd--9A=HI zPMEf~-fp0{tI!KbL3Eg)D{(Uco}cQR3wVrTHrhtiU?t^&uTw(>Y8sDb97_8NW~l;d z0A@LQlbsxtES)3J-g;u8?rHY6-wf(h~t`#y3Gw<3x;KJy3q&q?sWm91<5=U4Tc+XFHl7?H}V! zC|b`%0p(XhE||v( z-v}L%M@4c{D`h~IB6rNpMMj?8w?u^q)C_OPoV@$Rz3EOyAZ{xYB5?Ljs;thFi>E&6 zdhT`q^QXM4WxzDAf$LjB5+@4@%N^NA>NLlZ5eR+)fX{k(V!ioGXYjKn; za0bjK%->rOa@6X0N#W(`rH}YR1SH?u@4ue(g>jJ%==XzS8hS<5bTwcx{iaJh!z zE@B=wYe}C5bj|{1`lCvLv-&CBG7E%t2AJy*%6%wqaLtQE6)KU z7yN2uH^c>eGNJDJVNs`{w<`t4_tQMrCpNDWy7bI1tGD2)3^d_V=Nc1RT3 z*+{L;a%jZ;Kce0{p6dVq|8K~MLMq1&WmWbF2iasrGO|Ke_CCi z-di{_kG)46kj%8WGKP|TfszxUH0c?q~@ zB$NOrmRJ?;s!RX|S%x@`y3g5wFy;}pG0apHfGYqR#d>z;f^+e&PV@1?P?pcu#cxo- z0`aNKJRocu-O->na?|uyJNnw8GLESf-Cox6IO)7k?EH)XMC#pLJZ0+(8gaWLMw1T} zE&+Lv-5m|F85R^9B_)_L?ke0^arZfy(5f5qI#vWkbnr(DN_)A$W~JF3aR9iI%H}ou+?I=v0o67PIWYKzhJAm?hoo0Mev#cSRsj znEUXs(=>!Z2XE_gQk6dNbSx|fKObrc;=Dy*vf|~f26=qCmZYT)6~`L^1xIzgLei{S z;+;EOhBXyqfoDpkYNF=6c@XS3t^wuc@T3{b=+k~_PbC1Y-gnI5bQ5Xo(N%@SPM{%u=ie{r2Px@zf>Mb()6 zlOWz#2>|cji4S<*BB-hhy#R(Ks}p=)K(0}s0_hR{odxNC!ea;yi#GpZG4{Fz-YaN@ zP&9F1K?IvO6k89@FZ`-J@c=O|r3T(Ph4(A(%j22zLqL+-%u39M&f|^DVB4t;1Hv!i z;6dQewngBXWOs2rBMg7GClb5U-I%jUw213?1JG2wND7S*dKaaZq09x~GxL}|)lCbh z3sIxc*nhE}M4@n3L-P0y1JCdV-j&JyR0l-xv(n=juo%;R)GBv?F!+T7pf}+6y0tj! z8Ay{@N#_w@(sr!O-<{_lBqun2!YZv6?~9bkYo_(jtw$7_QhN3f$px(9n?m0Jg7ZT# z*DjCKJ(?5O0C)+L)et;6j7%c@ak(BGShSc(T>Lw)@Vr~RF~@KGLjws#6Cg&~{0z;( ztDf;W2(PLjFkhvyv@Tll0|ycCfXny~@&+8-ky7&w}@w5~Ha~$ySJ2-PvwEiJX zz&Mnkz-C0?)hKR@t*a_k3+-Cj$>p{D2;Q#~>ls-EBX2$Hs%^c05jTjS@WJVCKUF}X z1`tX${6(RVaJ*7hK3TKL zI^8bK0vWouMg068MGngA?Qw^O-yqmE6z>p!fC&=Sj^ItUSeVz&FX}EJ2GQcKTKM!E zBL$8z_l9HOi6d&+f4s20ZVa0Z+Wiwqu9M*kY^1)AZL#*<@WjBaQco9_k{un2RvINq zdC*S!c)K9TXMoG7f$sOqT7V?!aX5Fib)rk>#Wi}qi>VpBOH(AfiFJC=>dC{x%CG-m zL%{LD`2|Y4eqJ^g2>BI{0m8-z4*)4?X9*wfBi17?Eji|>SS!8gd{|_- zC(ImtWaK&hGU8|@C+++WcA2!aiPLp6(Y4w?^@vnt%1O2-{wy09i+QEtEnmj0hM@ui zU2x;gV<&-IzlP+gjkJLo)6B5?!#-cZes|z&1sFTAzX0tXV=Pe3jOwkXO zdGM^c0l3F9)vum!mu9_-r`~4*WN2MlDvw|G0xJ@(2FM;rZYCaDLTwSNc(w{x0bapX zJuMm*0-#RCAKo{jsFYXDA@ z>IA^atnRT3D_%7cS>pUJN0&4 zmTa*mO=)cpk?P@c0YEOX;9JVRPSpAx+a=)icQzy*kB_my-=MYy#R>-$^j^#9 z?q_((05Di6+nt;_FGuOZAVtmOe%pbx#`}0+MejNeB+a#l3HU>f8`vN@A1rS_61)XX^LMU@Q6_ zFDpvDD%ugBIaaSds_lDd)aMvE3xV5^^0_b1(ND)LrF|b4(6W;?sKn+g2hJ9#}b5!0kv>hT4!mA9f zv^WC~e=@VWJwX>fV+60+GHr6x+5ke8rikUuhVPcQ$(`L%I?u0I6Jki9aqe$?-5|n( zF&rF>`5z#`QuAthka>KmCO4RyxuwPhAcbS`6`c=!?-eS)%+oXwOyWsUT}VMWlx%yjv360}8H6@B`1eY(bi7Iu8&r6opVsMzF(bF1#mUmO; zV}I(p_CRc4#D47_CTZ`M++lR~DWql?0oipscCgxmhAEf@!dz6T4%r?b4f@`_!sdGr z-7j;bd%o_7j<%Au_R^8TP7(D{4YFTfoGB)VSreTLWzE;ktH)l3^PYn{h%eBJxxOXU z*2(BxN5@D+Q+`}+^G*IP<nPyO6X4VKsT|ldI+xN*Kz%B|Q0o`WHM2r%tg$O!?Rm-eu4_W%WLU!<|9Rk66N(ey-zi30>}8SU6I; zED?64?-Oo_SQv0ij=sCTCp}8$xP7zjVHGf68%94tDLJsc-G-)wd^|u(@L;v;GSwnu3EF64F-&KR#~>(X$xO+S_PFWI|#ZysOeoRuXP zkWHU7&7?78QsDO5q&bMVJVTAtYJEa}Nh1c^3A3;h_(-?&M(fJ^FwYM}fxNF}x9vnn z=L{n>+x{&2GV4I5kWMn;_ufKp9#*8C?r@E_Rl!4G_T9uBQfN60ol=n~3i5V$h?;=}!tnUfzzXigCbc2lS}1$N|ODvhx& zzO`uvtf)Up$rNb9U?Gi9ZYklREM`->b4AKgNA0ejH~WS!P!-;jP}guPb6)}RG+(;D zRG;ln%Heu*FH3u?px;_$g}7;XzBp?P?sdmI86drwm%uSBVd^d!D^I!9fvS8LyD zp^p#UcZ;KZ3yi(}jdOMyh44VUf9^mBe<&?lg@jNn@D+4ltxXFR^XS-LCNsql!oE8L z3%}_!e;-dJS#A1>^i35*H8!ixU}=PN61yGO#xhtjWQf+MdGmy;4_v14fV6}C9m5?A zr+2y|?CSJLjsZYIj;kt3Z}2kh=3-S=(pm@!EImGwN!AB;{7mxWX# z0=o4Ucb4r9qcoOBTAG%(9)Czl+u@O-dcULDl0hMDoTYI4>}0II@F=9h{7KuuHikq* z(6^%4aF=C&1F)UozG-6eW+sBBb|_naQv)g$#3C^(6ADj!Y=3q)PFYS>ZczL-9~!V$DKG83aeDnP zkau(h&9vuD_wK%bP|)+p0=cX}%;`;LYxe%=eR1bQQ}L+F8OQ)jvfCO2=05jO8Da90 zq4%-EH=ijnWW4e-4A~WyRO%|DQbfAUe@y!I$h{?CpQK}!IDiF)=&g$1%>Y4xxyEz5m$KYCB3|x?o(g)jg~Tt@2OZ=AY;jU@tt|f|DlOqN<)tT#5L5I}QsU z*Yz_BbS0jBF$#kiMY*Gu)Sj656F-b8+_J5@X+9369veNLH9C@|ad#M#{C&J;IL4$enz)`Y4=xlwWDZ8OOtsL=67n&JygvJ2Syy}7EOh! zni7U{9dM-QU{ya-e2#{{E-$gl!3Ukvy3Whbe7{IwJ+ckaSah27D8sYz58|HVCRL;K z&qTs02|EJHorRMrS5pKLBkZ;BU9>*|rV>F#yR%?S5#kH)34Op4@#F0|Tc{YbOlZa! zdI_L$v+Q6?P>p}iY;l`4{Ed3x;>5{!KdJXg_}k3a#^mqw*g#TH^oXVWt{V@))u;avZE9Z$yIZur9R_(d#MT!WRG zuu#7W$!hw>TZhar{RFv3uTZpcj=gNtk`QF7p~_2;Wwj*o`I_LI%mja2tM>R{49fiUu^Rld z24fWFN>t?!)YFeg>a!yS)a9*fbEmB&RltDHYyOE%RVxN3bj6Mz@-QG(7)G|33G4OGbhF6*`1NmJzjX^xv+#pz@pxjZW!M@3TfcNGtF)z z-1kiyHSM_uYs0&Lz7l>M0ecIg?A$dE`(4SZd78-byv`i!JE4Vd<-2FM76m@KxcmXB zn9Jyctd!vsDFp8>{5(iYXRi2-tnzl%Q^xMezohWsL<^r3%0q5d7asJB z|1r2?YCV9vzl0CSVP_73d9M!WohBwnEt0(74-DNmY_9$7`j8q<-7AC7v@BB;m(Ja?n;Enz*1e!jJY^d0H>i$F4EST);yhVl>|$bu_@NnBAt0Fb2`$A z;cBg-d6XwHD~+iWns1#rJwEmGBjnvA3Z?XnRoXH7XJx)_WNR>{$N%wPYpy7KBI2te z&MoD!r{VUD2hN8;ig%e%Ac|-{mPj62d|kutiN*(GRtY6DI0+GMuR*WxQbl1{k!68E z9ZfOP1IPDvqd8(L&<~OHK^w~7oS4q-H|2KNn_?<(!}3 zO&D@i_}N)7q44)f93=^J}P2ZV(VsPZV)GB&xu&a{NR5w?p9d0>cFf@ z%sghA>_*JKCY~c3ON0{*f!MP=o2p$asjvSPLq)l%;+i(ywFtLt`(Bekltg8tf(Yc zgVW!9RZ(?%o~t#gmlp%ByV{bjfdHo|=eCy_Uy^NF!*~Iyp|!XE;zj521^2(fr-S8E z*XH|AlM7Hxy(h3IrYhDoBYbv2%8lu5u_f(SvI;8Rf||-Z5%v4+2AJJAO@Yn4wH7LK z19bJ}#Lrj3Fg|2_lgB(xk0m8({7ox%zp9gwAH<>R~pU_@3sLV-LDxmKBi^n(CMP6n_aSTByly9_L zEtb{e`gZPsmZ<$j#XOD}5LIuxG(U6CdTg0?3g~MK`^5jCBVQ^{w!MC78c-M&|L*AH zledKtfBM(v&nXoN`@JLw-RleHzdK({zrf*=ptG5crA*E&7o#2D^gk>p8;7#@P5Iut zM8ev425JZ@wf+RrdsL{x83AWsG8YzAeQQKAv~uh$jD2haJ?Mm7bE^f{F)oMFe80wj zkfAWqPay-w^4`~HkCo|I$y?2H&M3@@76M9YZcMD2&=vIgB9Fhmc)|ojMnECyWS1u@ z_$Hx1bH&d6zt%?BFY1Jx%_pvDIKvy9teKEvIiOzK(7y4=+?<#?IPPa=N2%QH@epUn zyDV42(l?s@>kfM^VHaN$P_d$lY3h*P$H0KwWK5t1Pih@2^4i-}3e@bAbydzraavJ;qC?kY zj%?7p+jl>M>G2k<~GZC0t@+tx~<*_7g({)kG4~S@6;fQaS zPY6`0bbvLtdO6-qy2woLl@xBGFaIOmSS8ok!qrcmGOU~48Ftkn1fSP79~uINFR0M^ ztcdNjD3QsAdr!p&O!tR!wu(jn!Zp@3$-d_HuTS27Hc7(0Mze7^h)umK1?EM zTeP-WrX@7aGPyC{(Ra<@n{+otMbyuQGLjIgo1?1clSt7ydo!d!rM*aS*D*YbxZoU3 zi6D@Env$3w%g0TAh+SMkG;W12rcx>Vvv)jdh845@bo#Qtd58TIu@p-{|0n99V-T=C zeUgrASewICn$!+C@eu3TY~Meqb2#dA-psPHiY^qKE+U*$egzdpMgN3t!EP|f>Q!6G zvF=Q&irS`-Xs~H!8#>K<*BySY^Ci)KKgmyFXuh>(cdy&<8$XN0*ey2tzP90(Y4vO_ zfI65Bnl@|VD{rt)2b);d$Ax4fIfks|&znjE2n$HMAIc^yn+*%2*xv^&+sL#-HVJ}e z&e@VelUpe)x4kBaFUvG~tcm$g&an8hv|SjhzW?PFTx}jI68`f$YeCA8U#f8#hPaqY zthKpG+)P2@V(%5DMq#opA%dWIRMfQPruh2vwh)Rt5gaMciABG*Abm}aJ9RtpR)L2= z|FD|MDz`8qRGqUKZMzn+0p)EW^)jiz$+W}RpbltDRK53jB5I9IVcHSvS|qWxl&F$6 z?&_F?olT@_+^je#3{j%8tOBhNZO6$dzx|BW2McAikJ9JScd>S;mkGRXP$VIM5-c|A z`2DMPN5IT9koX3gMgDGj;7%9B^4%WyzR)C%>@@7|B*OZ@3gJeJC9@9yN%Z~83m4ejZJ^pZvn^cW3wqzzWZ06LhXMv5(@cz*GI&XXt+I2Ju;JF{;Lh$ z4DsG{MA6NL?^@S-+nqh#a%CU%WVlP1y`w(J9L#>dws_VLRvx#6*}gAaN*bAc-Q#CJ z%%g^($vE_1Xf2J+Q)2aKwN8I1p&?V()c?gvpp#~y^(vn*?Pd8#`Z?Iq1#&VEv-g)( zwO6LGWfW#hjy5-fogj3BSiYV5kNof$lX>@0`rwPdv=@;gDvUhWDTjUJr(__M@s?h$ zb+gNj9!zT9CwR2(Dd;&D&ZzLKuj9*_2M$%|&PEv<5l2M6i^X5 z8=qLZ;q0fVcv}BnkKYNho}=6~Th$b9Rklu~xdczF45z_e4Z3viy5YC_h~^c!S5RoH zsL*@oT<0{Bo6PoTnI`6*yGm{pnoT=i-o)O3A}foE#!kR`#B30;iIk~bWrvJMqhL!d zsupoSHs-=nsiA7TdtGftrP8Ag`NWwVvs`(N;Rz`r?e6ulrFfbFzQXI?J=`)^B{=r1 z{a3P(Gps^YeQ*O(UkM6!H{dzyAR%HLzk;{F-9XR*@dk{ayQ#vb37FDgtTQ4yL7nnY z%oYPgNrvpwf4i+D1uR>|2>HnytJew*MuESpOT%TNVG@SJ8UKs7PjQmBW#PFIw;8Wt zqL0v6iIL|Rd?iq%_4e$p<;m(!z(7wsdqe1r_g|>Keo8P^eFZtE3b%bdRkO8OQT#Mj z;AG{oXH-zx8K70<&OG}I5@6$BsU)1x5a%%M_icU%(gCdKVe4@i?W}$_zhi5JI}6V) zGdU51+wzNkX%b;+wK-)hrj^#{NU)o1H(Dgxz{@p@_c}50YjmN_zJBK zk^5++_SB0JBZ_5VT&xh_IW&#g9TYn1l<-7av23++aq(=HxhgjwRQC-&zlof2sqIYS zH?QUA6{d7xg71(m6j6FNsrmMG!4bzvmd297f&Z^%^1pTztG0GqsM829-09Cimd{}b z(0fGxm^tjEXbN3Ils=oV0MMNOx?pk!Y}IB0`yXrWs9G0Io{rYaX;88I2V ze;p|BJ!eDFN{c7mBaY}12kqRo1%9~=CodETPWXGHb%#TuYiLr*5JKq;t-bII2a?=P zPvM)PwvOD3cs!3;y4F(aw;0a(1Tv%U8sSHa8*62t^^I8E0z82cT;^4ef88N~c(6Hp zRUE=A3Y4RSbc{^KT*mjP1d+m!w$@;M)uL(dsnO>Gw7;l$>|Ld5Xg;Sy$ZbBHA~u79 zeWTuz6iFCp| zXwSWn+)pRJQR77W9hn`qu!Lt*@%jkPz-b>hkr;8J{u6J`tH|L&S7BLeEuRC9Gagui zSfM(AZT;&cA9I-g6RHw?*(p%D-Y2*PVeqdXYB|a;tBbFAG$N51EB}aB!6BCUyFOT( zTc5V@3fOHkFK+=hcX6l&-aiPhaOG9~JKPPlJzzK^69EHZ2-RdgUYLmwOJK`lJ298* z@s&Je+{B@31@|fMh7#z+k|)|b68s5_m)rzeQqW)4oKSssJ8HCXi5Nt{YpFNBP(#7oBY~24Ev2_)WoNcN4okjJ4I3xoZ zKyRCIPWl(e@c8A%q*mA!t-Ex8+TS12@I13(0T=ZT7XVrqRSwBcDOo$Y*-TNqrI}^u z{Q{u;4Dp~w{t<0JghGV1%27s1o>(V>hMb~WE!~b_6EmvrWN$R^nK+Zo*!G}G*80W9 z04s_cC=^{LEaGGFO%l-84I>G`)9XusUedC)mKUJzD1syLJp+2sX0nJk^56u5C0B2I z&ms3Y<*R<6Q5df#Jds&z>E?QL6@V8Vaf-z0wt8l>T1$x;pl2-l_fKFKUOI*E6(1&T zB8WD^*ijsKl_@}!g26)qNeiC*&lx{lEWWXNx^5+9TLd_MyfJ{K z^M74QA7}8wjF5kC%sf&wdmZ$e7C~#5bFWXz|Gzi({|h?YFXNxQSNC&IAXv~P*B03W zjmS}D);J@NiNhp{YFQ`%eR5u)bI({2(r=b%p25e0v$}bWyno%Bzq;nVQKl=p@KLtf zZ^}*l!0*IL-kv}O2fGH3Z1o(_;7xC{q&Zd@BA44g zD)Y7LifPvV9wn^dzc%VlUE0mxl^d1_G|3Ks4ksOX0Io_Mv{Om|$0E*s%*bH{FXKZuuS60HIqvSmTAkOxZWU3Y#_kC*A_-QEp;3J&>8j0bxqX@3-Fo-WIH& zx8UNeD*1KS;3rUX)V(|a*4sjOZGJEb;CBRBCYT0;rJb|ZGK6RQl&k>#!+$SZ0)iE; zFV(4_H2TF5z+yH`@LyiiKOqQ^LRci(WZV3w(Rd8@p+$tysPzTDfiRr#URze4wD9c35l3!`#HB@e}i*wy6MDm>MPoDUZcW4g8L>|oh$B@aX^}qAu_iP#qEq8*RiEh_qsY)4n%kvycVN(2K8iZ^W7oZ&wVd91k1+o5lrKH28I-0|TyOtE^LD zjR7)aPSgnw9L`?q_U+~4)MJp+sKG2EGef_5K*klsxYf)3-Ti7i; zL=n3{(e%ZoGbkiB>#ly5k5|orJ`Y~UP_|hMh-s6yY)qptN6sSs%oz8XabwEr58;K@ zk97|rAp+_%#uH#lpuY#{w&Mm=(=m7h4MUfX2Ns}D>sYkm0Iui_Aa7p;gsb%v#{Tnr zsJhw<^~EC-(TZ-VF|&;_W<+%N(Z$~Z{%OIXs8>7t@%5)!!5ceI-340h!)mGPM4pG{ zFjR0Xns3?Q0cwqHaV2AQlp3F9pb|jXv&U4TaSv5ysoLn&39G5I0_LP`!nh8i^%JLtfoA@!2T>?RTsEUFER6 z&Y8pVivR5yg)T*xFm_|0OQHzA%b4?7L$ZqAbIKXtgMVz5=IbGPak|%KCApVKTW2wu znbi1Vd3@Bs(Fl-HLimB$!tzQYvn2qk^(eZq3pv7?>Hb9r5CsTzAf=f=9&-{)iVWlx zEXGG8;)xe9pMNRE$NJX#WF?2E$allGE11qf@89JHj-Qu(;LL?;fpW~x-M9*L79M-W z#dOjik{>cI+=Um-{llW+hhg1oeo@>+W#Es7b?}rIGe4{6GZq4dS^4z}1AtRe*Lv;3 zhpXT6-Fk>0>QVzI+UBX`4${cmaJ&uKA_Z-uQabQM*jc)>k5VPo& z>~1FG3(%#^Wv-Q%UDNdVrE#zMmE zt*lEagZzn~jD3!W)cLs&&dIYX)P5s1nf0NU$-33ada1Ls#_Fqdd(7t8kK=;RU=JVd zGF4O0>=mmD^axr6T)PVk8f#o2L&&r8uIG?#U#bN{VjiZmWyLOonUm4#Eij_-vg=x3 z)O;Mk4p#f(-2qXLKo9;0NGZR6h{NN#)CtX#<;4!CL1eUA9>U~T$?aR4(Miu&fhVJZ znUSjN_`*j)oYfWOW3~o7Ze%s}m3f3v{ukEP&?^>tZFRjgHcGYtE9a0S376H|?T*Oa zg1V=|v_xI|{zZ+5h6KL6Nj|!xn(X&hNvKBI00)){|2yA zBn_Tej5jgH!$f{xmHcRa61bBcKUGl)akUf`sQbplCos+{W>iX%i!b_m5kP8gw9rK# z1AE9Ke2sz8wcf{J0q>yvnTX<3L`4I^Rb)Z>Z6DKRW_rYme(PZ!{ID}z`Pw6Jr-Ktx zjE+fjQDVoTTV%#(#PyMcKb0}`RqgD?4r=d7kMN{j*(QrijO^q|SGbeR6+8TFkMRTe zz3v#tCUxV`rz=7N2NN1r@_R=hvP1D{`!=kYA`PG#pDCO(fso7$=!wCd2j!jH_rUL{ zLWwyO-0!h`-Ctitk|1-YH0?+e*)T@`+L>1S?eZI0d|1EjN=k9B116w8(mmi6a0wDS z#)LZtRhh_?z^=i%Aek8*Kdwu>M{O4))rcnxr`%-jQA&2Iz3>S8B#NaUH~ifL$p-@E zqF~}(Lf@TmlL7|P)TTNr8!9QNp|AAQGd%I|Ac1D@=X|8NP9G=p;;SIyPV<(b1!v{&aidongeUl`{t!*GF|m<_zW^qnGHt3~$JZv`@W;wd(qt z+Q@{#K9ISXxb_Hr3$T1w&A3JQRrPOp;P1{UHyTY{h_>EiXK;H0&>Rda!|i!o zp_onrAJ4XD-Cinfo}3ciP1Nrt@$Az9n#PBRK#x+F{mbAjS#G4yd>-tZ%*8f=WqGY~ zH(t&+Z-B4jk%QcAYPz?@534*L^@F9Ac|PnZWl1m?vdZ!D#sH!2STQ-8$19sAz^egn zOiZA_AU!0ms)6AK$U?81Gje>aG*OYf!nGK=Na;1GM9Uq@mgU=piV2S@rd)*9+h3n3 zw2%-9GKNudb$`qiF#nEzQooN76k-y%8jg;_j#|rgI!%F5uOX{FHO79HFW+}9kumm`@X1@^9VIT(ogs&7;OwC z;VnLSC!yecTlZ-h+6Y&ddY@tKgbYUC;5^J3uh=$<<8x<5$;Pxb8;K|ognfH{OFhSU z;PMx=msK}_jroHfZ5h<7PxX{n*mN2u7|VpxUW|ntVG?J1s@!kELNc4fxi$h=Ho$%H zqnCEsXuxzX6}xyK-efZcE4SpMSKEStJ+m{}MANkWHDH)D4{LcZmzRc|R?}aDYrisI z@WAP?kXLA()<2?J0qv@JS={nVt@hyP=lqa}0ba>iSCi#*TdzM;%?>wg27b83U2WCh zgL)mIL!!e=zK4`eQVR3)rB%Pq<+}un$Fo9SUj0aEafRSBGj9QcFy9l=ktY)CUc0@H_jtCX^Ri_#NABh%*HL|j&RR-2)k= z|K_*nkZ+r*@^0lpoa9zgy^*VbJ5N;Xe9vav{DQCN#<}CF#@K4|6M`24rUaCWNeDrH zbsU}Tt!k<{d8^9-eQQ-g>lN9T8!MZmJwd&c^lrrw)6{0b63kMb3VVDtFwkvdaH$z!&hV zP1p-Hw+i|pcMdCGQ}8kFE7?5SUp*9}>VIl_KapPEu+rGlCO@I(hQd7FGuQiWk4yHI zk*EA~Q1Xag>pwXEQ|m;Bq?N9mihuFEjQ)bf&Q7O8tGl7wfV_M4d;skeTXaRt|3%H# z87ptd1PL#jcS2a}BWP|3zqv(%NzKMi@zu$e0>1KC|C2W2jrrI?@xu6&cI#cX$EL8F zjdFwUDm3=lCYILx5V}yIHqz+1;*z4gp^*tPJvT-b4oYpH6z~)}-@i^Jxw{*t;qx_^ zt%GjfQTcJ%i=SJ-m> zjY(-XOZCNx`#m|HT*>lzTcpM9ATa|c63%Ib{FhN3pqb{q*PqU&3rMQs2J{`>1)48n z*l+R8a;@{C1@9=nNGBt>qx)h)l?%*N#yhT6&HL)7gr}K0GABX!S}!dN(XV%e2q<5A( z;1#MJzbi3Dn7Kyc*h);PB=!`+oTsbW`A3i0*{er zKQAfIJ~U6jK$Xds@2UODqlc+$k2>M5hFiOq$ukkp9m@^+aUw}G<&76TqF&hIqWR_v zitIddr8Ci2%hl|uAjc(`<}*pPAXUw-BiqX=a`2n~vKpFee(t!R=lHHy1%-e^w6S95%Ah zk1SrpJeKq3P9&sD5i5_H52=#gnD~2d!TmF_P>jSuv^nhO@C~t-==fEJgLKb49*EE2 zNWd(*X`S8{8nZ+S6-6GpXv!j2MVI38zNai{;aSVofIh z(iO7LH+kMc%)W*)f8k?igmkn72Tw~P4a0ZD$Ve3g0^}gPVvs?vH#8PxE0*HTsM&&n zSQMigqvFcjA3Zw1teuR5-w7hwe;8HQ(Xl|=4oT##ffkYMW^moOd1jx5Sg zKD&yHz1^wUqarVqt|R6&dv{MedOU|`pLo)QtW3`-IKg{~QJ8{lGv0e-;n&$4xsGt$ zEJ`$L@0Q7C29#1WBGkFS$|~8G_0__FM;m`6+Es=rYu>L0wWPNeb8YJl7IL%Fv*+wb z&&6O25B7(8RLtJ97hXW3gFUV`#Qp>?J;X8zuad{)Ksz%I2+k z@tUQ-nUbwiEwrwpp+*j(*Y?Me){AEXpZLkXB-LT8BTevN?qoaji9aKUIcbWCB$#ENq+R2=-_krI-0_3*mTWtp97MB6vL)F2b-|S_>OLgKSYl*wiRNdT zehtj7g;u~Ll;M5UyHAQ^vt1WRW|=-O_+bjN^bh5(J#CYMUl@ODvK1CR=uvOKHQoFO zp*;Rcx~wxGV1vLW&~^1Jj%S`f2@S*S&M`i%?i>OFuvxJ~6%4KHV={8GE!CG8R=J@4VjeuEF#@d-tTBT)AYy5!2O=md_=n z*(FvEg}vO&a>>^(03h)2&Lo4z^D0L7INvZCvl36S)F)}D`opDQA6*t_dl9Q)CdiHa z!Mno8sF+b0@PI|I2^oOd{vpVsxcosOiFc>M9CAu#W4$1R0?`Kv2@j*}{KaGqIj?g5 z>ejq2<#ue!HN%gOM6u{M9W-Gq*Kb|ZzOPKks(c^&;MvENjD-~G4kewZ?@vU!YSL6@ z%4jU(P}7N+!qq)rl$T|-%g9EdSiN`Cf$aS+1rO0dUp`U2uA&ynNF9f8d|+mdOuNh@ z_zODr)}iD2R83)k&{QCl1)-*AaBmvzuu=%-%LbVW1B*m zpF?yHr42JBHpE;5-=-d1txzTbyiLcOFx{&HJkBS|?vcb`~AI1ecT$YQjZ&7*GgpK*jcKbR( zV52oW6y}8=OFAvsHQ1TlzW`HPiD?;rwyl}?d9zRAch-j-b%qO90F>Yg&OXvG##uNl2Uqf@wVU^;k&pzZpuz3R8yH?{~ z9kDiVcw-`ri}WsO1jc_mz1NhFFz0)=UXi#2?`fM$fBkw-FV|u50T8zNAS1^}2ts}L zhHr=7WMsrT%xMmVPovcZc9USEq17QGV1{W?+ zN4ck)onGg0GmkoV`HOtGadyVRCFqJXAs$yoi&cC0H|(G*QxDQke?oU6?I7uBn$AMv zMFn5dtGfB^^Bv>SVjT>X#ct$X=C4C;s==n=WCO~~@8!m;dZAnLvt?pjDj^QMCowY~ zsku`l%PS6>6Qr&5i`S;OpwIHhR|0t7*L~zqH*He?pxa0~>X*D?&lpN@EFfbFIq-TE z)V)(eEK9=>N+;9G2a_fyl4wKA$cEhP!D>uxhaPx5nxryla)-S`6t29yQzF3M_ku*^ z_Vt{KxZLgdKW;_v7_<|8=>)0v50kxv?R|?}J@reJDTU2^x0n05m)0z}CB_Oh`^xz< znr@T@QZO@ePkM>&eu|~fI@juzuMtWMznHElQRgEL+oO;NSpXh;6NXH9)^ zs{8CnDtgeAJ;!b524}6%Ddj67=0#sl^rV1+l_wmV3|(_)Tm3@!xzBB`UEKJ#BIlnL z**V{wW^NSaRU$sMghR58BL3J?g?*jv*90StS3ezv&NjJnY=zv@vxt4^QhD8(pNm|R z?}*U{ab43a4Gle4wq!9}>PEtsIA=M#;Kyc$GrSi~!g`;$75+)HXfLr&?&OChuwO?1~WK80)# z{Sz^=%}ZHC5Z)*mbkHDL<8UmG4n%0LB<@YRs(u|Pcgb6iI{HyhuJBc9GTzlWwO2ke zfr4=+@ryt|RTteWcSOFI0)aI9Q}){PwwS`o2U#z8)K~UrrHXC0lvaXt^Lf6a10^O& z*tWWMxv4hu1fCP6VLx2qmO~yTc!s7Jq=vFQBBrk@dSJEvHJrP1OLR@2=_{XhqXZ~L z?~1v2sUU)nuS-%YVrC;VsFbWe8X9bgwQ1$%FJMKt%%aA7kDnBt(Xr*gx;wo`%0K*s zNnTHkC0d!5Vyk_d8jSls=ims(yv%Z$>mXO;f%Q-DuER;8){u2C7vdX?*X`SlHUl`U z-kD-_A(QQYqRn1=bL@V|y@WMYUHBVazUqNB+MQn-;d~IfNI*eyYcY%u#n`sn;8r(E zS*;)lQxYe_&KD8ogj9s^@_Y{A*EFZ&xz2)k$_1LbGrlap*K=S-*AM6aG9DUVLc=zl zMNQ`0V6gZ`zr~xBaig9^68*8aJ=Jzw?Y`y5+e@5EjB8)MAL(=W>Bp3WbHr{tF#`{a zsRts#w+8DZSsTW{!+a#&_c{JG9400?@Sy%{N;q!9hdb-F9PiSOk=Z?M28pgFD_`vU z*Qx@WZFQ1jFrVVb8Xa77JV7h_uo;~1{IG$2$T4g%(7xI8&JX@|g&l{;Uz$!|p z$!s+9*OJgfx5N4A!(_Cm4V|R@W(;O||T0iz$RKqG9Y$NfdScfi~IO>?vx zo!Hlj+cFQvn*)$#I&HmH~hkRv<3L^X9FzEi3%>M{O?YM_-F3(!L7r;OL3vggmPAAR|V zczj>;W|ZyzYj%zYPTt$%ZC3{5-@yO;*4VjnCsbqBO)h~tkXL@z6@lElb+hB18+|N# ziWDcohp$>WqkJVnFN^_u_)1|967S-GQ>`U60;D3Hx z+OS5l^=a>LkW$4MYJLqz&DO{ZE*;Dh>e@JI3Ig|~hXR>D55zbl#&SdU+PAdCmMdd) zPAs2ENwH?>VsaHZOaw45?Jm!*W6m&*EjLZ)&K4!Kp)U|&xwp2ow^+8^zlKT|3Vfo_ znrguzw>2+?-D7n~66Z)BNXJgIUT?*z3mK->VrQrGK5JnMkA5yjZlSfmxz*W`&Z#{~ zI^v@Ig&7c4M#QE}e?j?J{*C{pC8aWgIBkG*xG&VPJ1P;SRlAG9)5M3U*^}$|JiF(m z|1i%5v?^KhdSBWVPH5|Dmeq{gd$OE5tIQcJHQR92boWoOF(=Wc>Ei6lni6ze!*S0} z*DOzEnRZN6BMLq$koLvJaMEks>v?kDw--NDLEK&LC&2i%_q%9jznE7cEUtU6!6TBf z1xOp#+|%r^FvdR?DBmPSdcEE~GrA|#jE`_0g`3KIRn-nSmqIxt&cXMnWPuiLUDH?2 zvtjB_V6?OQcMS#S6LegAWp69WyqGfm0y~Yiud>>dt+I+-^TBnVaWR6H#_ux3dR3Hf zYB+_m*&w{qv5u#=mg;yXaYlVU^6dEMWVWKv(lF^bI{#0TX3SKInC_2_ zB{}nVR<9}}D+65Y5N+@wzT*O|Nf6Z`{%irEu9{xMrRK%RaUz!z#!IQ0{z4mD*zY-Z z?}Pclh;HB3LuMZ_MR&Az!#<_8l5wppJse7UR zJ7Iz){^pEFe6!?<@4n}x&TRTn*yfog!P}9%6xi;sSn990^cYsOcWx6%&kP$uSAM*P z@Y3dYgMLih9yLr_C8s^ddDfltj9D!DVNYiBMp=Z;vC_wVLdU+^DBt_Wl2Y7#O6JnZ zu#3Lt`sC)IfiB0){Ue3d+#>8>6D6A|XRY6E2eVkAQy$hTuDP({?uQGB1BUM!FP0KR z*=1xa%V*f4Sv#c@DsPIUpAjHA2%_o57*!PWd<>g9yn;2KzfmT7?8dnIM;sUdC@Mqq zPM&QSi)_UA5A9$@IX=k#E%N;UeC~>Cmq}CKS-iHTL>(o}!d|vb~fm=-$b#z(#iGtsukeSp}p62X_giDUi31iQPG0I@pz{_oR|m zN9CY2R$55OUPcdUH(whhe3;`ljN(l7dHt-5=OmgazmP(-yf3OJ z_bN~F|0C+H!=l>S|KX!_NQw*~Lx+O2wB5XhViAGYE$X zdD5=lYu6nlxY*iZ@X17G9gXwk|0GqnOZ}ny&iX@Kq^X`*Rnl*W*Ez7*j1+^x##jsN zK*!vdl^ z4U+IN;}SnXGYcA@_AxE~I~`a34vBgdbo5`n5teVnH9OvpX6F^L4V>q~i15XtL3@f1 zC9PT3lbAy0-tQd0h>4hIdweJiu2%Ql)Fu!3-j**u4w1C_QBSTJsnk7?Q1a|)*;nNE zP-2>42b_tPId5Knp}MN02W&e}I%>wIr2Hmh&YGP{V5B-E8BN~i$u(Q=ZpgBA=$aj5{(B#B?)>57NzkeHyq`)3mfSir;8$riYI!C! zVULv@7kv0uOLl6cf*!r{R6HpmA;>s}m@P~RD;?JSC|r^P0o zZ-Kty6q9O`k&o%NdkZQUcaDy=rCL~0N~x_Md-CaK2?@pp_G0YyVlj5{FA*~MTMaOi z5>RIaAbpLA-v4C&bCEH)_B_3MhAp6q98A;Y#UQURhXFwWcG09I@<4W18jhC1O8qgg+_OaV$n##AbwSjhynmC2*xc|1D4$H`w4IT zgR9tB+f-u{c6fofPlOyI1WC*C>s3Ypc=Q@=qV%{u)84bM=)etpnG4C|>`Fh67AMQx z`IV(Xp}7w>31Z)g^u6oyfJ-g_oH?xnd1ZGN6g+u25O5_(%C}K#TVaCxdGKNI7;XE} z_0`VxFo-Kkl>1AJEU6%RQwh{+AC6}Oq=|%Qz-tk|1pvHwyc|y{jx0Ynsn{DHV?YMc za7`+8!U*&XKBpJ(SBFEu^RnD8vn+A|Qc7YQnz_mw1OPZ5pbCZKz+a@soPsI1OD%q5 zKsPVsu{P*Od@7L=5hUQ8aWHFNQo29)we?n(@1LTK*+t(hpio7WW!UF$C;x@wGw2Yu(VTP!WD?5kmmv^Q;v)yF!n!5dAV42FG`1*B|?vn;(Q{gkn&I3u^ z4>27mg5(aXJXxR(jdrf!VzQxmPtY3V1UphAV3{+f8%vk$VSzyI+%Y*0IyP_+r)uesxBp zrYG%rA#eCkkTZ%)K3s`lvVwww zxQ&p3=mT=a#b`l|3E)o{1_G$0Mg052&cJ}%un zcXU{BZffccO6-=xHmp30c5OxaWUj)u{vaCW15hoE8C%@mvw86rtip#xu%PgGiW)Xv z`vhh79;@Kj3q4Hl9Tgk!%QpzhLuR)@;!&2IGhpkaS7!yu*}umb0@>_Jk5zk9IIC=@ zThMo}_EdMqx^HQPfYF#G&RrT zOrZiI0l5-I3(A>lJltUOQugy4BuTaCV>{>AThFvUWKnWhoY*m;xU1UqvzA< zn|pZ{X#7y<%we551PM@feuTxiBEHJlRH>WuxfBB+&n&Tzi29mD1@6mz!by+C>}{nZw@FFl_7mI(49xQ;n4LICba&&IbtWY z(ISh&?@C~uv`3vZ*B+gm4g_F8hSJlelVi*l(-hN0?w7Uszv;!|(GMhNz-wcSPt4?r z9rym0TM)YTvx(ko+b)U6O>bIBAEU>!W=|ebb8y1h2FNhQK$R;-9Xw~Y_|_XfQ_#Y} z^sf5NMHO^ihai<^nec(k>S7qP%sG%MYk6G=mL7^+;dx_-Fq$Cl-#vvb`297X+Y*^#}=m}5n< zXn4K_wo6cdpFk$qx#}s)Y(>qU;!iFH?!y^Bgkxr8k9>nymD3n1g}}k)=f|wZle~a^ zBoPnKW(LDzcO)&p6F%SV-eC0jQQFXFn&o~(a#Z$Fm-ER}{CvH;xf+(y*7VsD^|Ilm ze{bIQCHH{C8Q~wJJr)#tw46TV6^1>?&6xjqD+f<8Xayu{C3D?#i{K))NACl%>b+Fz zzhkRF%fMv#!x)qu?gUz-N>tw*%SFPC+#PYH7pH@jHIx3B5c_pXc{joDM-)>$x=hpG zD&m3WVG>Cj_;czve{C4y)ULf_Un!+RIq6hsO&B4 zm%hF@y&-ma_;VHJZdF=)v>RYYDycKkmM6(NIa)7SOql!n^>CBELf3!%I}-Fc4`q^( z3*$-7jner}B6po%2k7l&KijW4j+v;D+}m8S+an=2&TcDJn_*{$_o) z0jMjNCo}B0ltbTMaO5HWOEvdh_cM1WtABo_eT>kcXEaAHsGv?}A39*^M^uM--8&|{ zN-6Sl{;<00Hb5jfnSB~Cxv$mpM97ZF>3u;(y`>UEnfHyb3j%=dYdm#Gf_i$`z0%O!{g5kzH<2 zjAxQ0B%ZVQ&XN>$CN6bV7oLkc3MH&9wdF1`gaA z-N%K!yC>vqzBn}D{v1EHhCV%*n1=g*^`%vPl7x|dAE~H@!jIyd;l9RedE^Rmo%4Xu zk0)R(l8F*qKU){HWIi5Wki{&Z)@w;qB)vIg!|GDm)REbLYXIkmy0lGqyyHi2=4h`R zJU8E^HT(zGgBzFRRh=)rudp&-iW{8}e1%(SiH2+p!71$1C*|;EIAk9^1aNKiFx+Y) z#E1arqYCO5`HX+!tU|JO`w~b5{2rpNP+Kd4KcO66kj*P@hNz?t*+d0Yzxz45uE(<) z@g|>1e1xxDFxn%uo{TTY!Ya&%g7sCeTtXJ>VdSwg!mWUrXZ*N}(QDr~8mlE{djjd) zE*gcZG3N+7v?IkBy^k2vMD14BU%PcJaS#j%a$0cIhQZcWj^-Y4ZE53bd1T76EOmzr zP5S#a-@b_7F?i-Tb^%|aZ9nELq{vJ-xa|SY#DHnKQ9x!ad5d2B(AxT?{@dP;2Ygd( z7r0v?+C1aBIx8X6=4oBRTCW`bRV|=={Zwf_!Vpr?7$Y(xx&_RYVtYXM4b{^n1AC#n z(Mf5`8Zsy{pNT~fTh`rEfKUTc+}dGK;>V+q#ea(S8bK;x%rK~CQi$4=${uXVFnU>` ze$necSQa#(hkpm3B!$)mFLE_V=b`=#zgP||O8BbIB`eHNuky`g`y{JNXLfaK3tBVTUcgh9qW+~M z+ZQ0A-`O-ywCUeq%5za${K`9ShT^+Cx5ix%=Z!Zt;mR_Iz{L0%ESq${{LPQ{c;_&EFTRXXMtZy{JiE*afz@LPH;$z>arM& zt~=YibDp2kskY_oCy-ga>c?C!BHO7)?IFP=~(Yc<-fF ztyn9QJvM(_{ySdh>R(AuMx%@SsEi1_WC`c`UPQRmEHAhezMWTt9>wb~Qo?^1RO~+e zJ37v~LkV7OZB|q4EUE4RT*ZT>yS`@{wCiSeUNdzTFPUbN8cSc`>Bo;j%bVl+n{Z>JE(YCG}b2W3k(T7ppRv$ZFU%tqhbmFKeA z2bGaX3N}`ZtGGW1tQ7&p<%06I!9^(4$_FO}151m9BHynNP((B7M)z3E&Feg5EHZyS z!;V7`6vwh-4Jy*_%;uca7E;8>jOs8}Zyd^EsYmM;-(cpTesSMUoGBIm>*z@lnfxo~ zOU^w0AH9BQa1lQ(IcPYlHIl2O1*aaB=#4WiPIO<^f#*x>9u&9$ig3d+ix2P8t*|>s z38V=44}jd5+wc`j9&R7(Ap72y3~RS1p;Qm}cIk5WR8id_gt#f76XZl;AZJ`>hCQ=w zd{|YwJ8rPO+sw$lt*b-;@!iWGh?@lH?lI!saJdW2S$e$Yo%J{#%X|xmuJ~$i63K7= ze1a~Q?~ZT6FlTFD_W<;*E~Z37OBkmtwtJQ4x8FnOUZM_eI4~Dt@On$nR2wLVBwhml z$Ld~mijh?W2-@X(n1cPIP!31>X=zD)60uLf zo|Y|KHq!E_-g=R^5kQ9P01?_j z-g1arB%b&6o&R;v*^=QeB6XIn(>V2|kbHwwQSq>{k2Zr6y~2IvK7e$<`)dh?u9A*i zg7)Kc=TtVwM4Nk{H>*lADT(;U!@CBVpwgFGB@JD@2ffltu2AZ}y9XHHoQE97Z<)&x zPzf(+dSiI7mK#^IC6g^Fi})KO%Mt1QtH_WGIsk(D^V}Acks7QQBs|4EjlicFqbZ{a zTLFjIt%mygp=U2wHEK^PZ#Ig4)<{3a7sC@xOJ!Wn(@SD}4WMrQV zm5$7RWlsApB%$8B2P3}CIk&ErDt#gB#{NvRNC0-&PPmEF=Mmbhpcad= z`jy_N`#?&|OR98N`GjE)gnlfeS}Ci+Hv`xE?WKO%R3!t5=H-kT*pMjxm5}Lrg>mNmsc9_4U%mZ(-_cOvY0RZ?+nr5rOMOL59Z>)1Re1mPq`OB ziMwJ#PR*A`svx(<&~PQGyIgnA8Q8Ivp;nlL#ti5kXjLciZhSubQ?kk*Ln6MG?Qyz%Jyk;DoTA1>X8Vt3_h@$!3lL7;SJ!5o;98He^-jTsQReq{u@>8}yOQ9h z4)`B^6669_v(8X)kf8wDpuu-;*v7^?*zD!5YZquw%#RuBe~e+z^cd#;QvABSN!0Y&w~&?7r%Gw&3DCaq544J4OX)sUm$X#lsUDOy zuJhY+cH5~Xm zkbca+I<54ZT1OZtO;+Mz`AanQ#9s4y5Ne0HodwgK*Kb;Afb~zdr!O7gG5E!Et&xXx z23S^8(jg=uDbhj(uec0?%2d;ibdf&Lm9sgwI!yjl!=7|iZC9u^3^g$Y$G&Z8J|S2C zvk8AssIL1s@mQ#8RhBCLYf;-gp7e);Mc>`~#n*qi)Ador1_ac1k4)S#wt17GC z7o@Xt9gNT+mNLQ8{&ViVNIrIYpgmeM?b9KIBE7w!U((-MXVNY+4-(u-tf$Vl`3AzK= ztmurFCJG{6TQ>CRR>o8fM;{9iJp9R`-M}n)Es){EiM$_4-SbrUUasT*V%Z zayih!8R&TzW`t|y2991{e^#_3N)%?T0Y=-7hQ=Dc9vP;(&tZ!WM2Ulg28yhtd$+4w zK^lKTti*QRR3+Jh-5e7pl&3+)Bw&psMofmxL!egL?Xmb-!!({gV&n}dP%L&~RC~WD z8;OXQ6F%v*6U$*Nf1Etm6Ri1b^JLa=(C+|055t#7GlW~PMG%}ZSINK^uI4N1s3(D%ld>}y3h@7C(C7-ckxuPX3=RuNyDphtlvg;% zQCK)^hcu)ddTS_y)d#-pVk|6ZfUS-?+C8W(F8sz*gg!gfIqc8X>Sv3JbP&`mFdBul zLJL-;b0~-fXUe#kCHfG%!*6%`l)&>1iXxGJqE?PLt%|F|intBg3T#niQjZaTN8#Jo z-4oV)kw$1FttqiKWBE3ejAE_vki4w#plD4gydd!ZeW!x+on~pU2Pv;ZPW0_>Wf1c- zX6ml)e}fjq0W-qYMU32Pec!uK{b(28;v4v0I&o zS!iV8lBg!O9%xbi_eTOq8t(hh{5!?8j{E`D>Ig~%mAMU^QloE=!OuL?p7sXFyL0`2 zdQ<@R)v@;h2dU%#ko0tbu}T8Y$`|w}%g~^bFTnaP_rJrzT1Z7Q9?y&#^lj0XRsZYT zpglPzFeUkOxj11S0=y`oVj;tBvB+)ZpERSI{Ri^;*?>Lu#s7W2fS2Kj_Hrf92t+Iv zzqaosOrXJh;L!t375E>wI$-pvrf$lhPaQg~0X>xB+kJ363h#jmPAU^*?fa}H{sUM6 zGn5BT@f>KoWP~~T17qPom>+y?Ht_5LuLDLE03HaT-~d6}I>i^z5Rw053l`rsp01>W zAMN;Wz>~(7^##543eD0f=$dQmPDlKF!)>+?r)e9W|kY!!05tIaC z9wm{`D4bIiqv0^)9^k?(oa3NLc9jadc4X=R=rt+5|I84K{2?z+1CY7Ls9Vy>5!*y z9n5p-H!nMurQ&m6I9adOZ~W}I?R*K~b#KEClSI3LRpR?>5Jw0z#4#%_*JeO@)Y zQ2BWVL{cQxGO_ycbk;V5$Ow%qXOn)V+AM<@_vI{*7c&k3mrKq}VEkMMlWtTL={@y? z4G5S*1IT%z1J1poSKOKK@%fL@V-a1w!lT~2_&IL=%U={iFYC?EL0c{MiF!bC^Yp9W zK+~2Xov6W{MqPRTNKMMuxDM1a>50`2>Zcd&H7R1=TUz~ZUvo2AH#&jCerM%qE8=MT zRFdrM>!2*ra%B`>mHSZWb^&U|UzD`ZcP8x10f=%Ga{@+cZ7|sJ{nR|*B}Y%7XGbhd zT={>cGhaI(=w2g5*YgsGq3I3PAcZkc-@us7n*^F~HD1>Ueiu{}q(jNN+$zv`6Gw}K zrA_cxc3{=@|J>_h$cnKFikdqaAMj6sh>z+PT)L%83SOw_`3VLCrhT9|3ye42r==p1I=gO6Si`r<}>)Pza^W=+~)^o_s#{AVo?ef_KRS7 z2yKB1)DDci_`d1^V8GAZ(Hl2&>cJQ5WURMNfcc@$t|!Zvry-5?(z^S--W>45 zr*(kERqxMnD;urleIHwQdQ{t-+34IJOQtxsbm4>oi|Dzg_FfgaF!lgY%A|)d=cp8p zm!JNq<7mRehTPH#2IfA?cTe-w`@F+R&wVAkT<$Siw1H{0(5u1F1V^9z!NE z-oFTE7|QB3LVUq7!2a&Vdm+F&gW_DjrEOU51j?m2yE<-`X0G%E`8v3jOb;Njdc3N`tIt8HY1AR+^1O-KTMurDQ@dVA*8?6~ zP{TrRk_ex;^As$>^tPEhrY~DE?#$5$w_sB)?GWzCy(VLhcG=;#d;(RlGbECKMqQ3f zDl|9U0jt>$XPkZ5Wf?vhD}rWPZ+r+6C2VqPKuc~*+6`hLQdanEs?h!$FaE*0 z4j#&7{j@u&-&Xb|7~rAQ$Nfp|C`mMQSw3rH{=qxXQR9PEn&)Xu4?h3WG1jVA`~ZMn z;i$lwnX#jek_DffPaRjLQ8#J`*bCR(zEKAA?`4sD~8msdEiOqnMJ8P%b`>y62ch=}?&47lD z?edLXo@Fua)z(k?{UOtWKxwk#uuxeXeC)3`Xxv8!rVm0msI-#wMYb>Ur5b<7?QzL+ zzoOssAQX%}Oda{!=MhS#CAjB0+Q;%XTm$`yIxws;HF*GT8tr$px2;OgTF7U}=S3+0 ztOR^bR=IuFASAF8s zq*dca&@7W&$sZ-tQNbxE$!5SJlQhIda^z5XQ1qm}xwcAZmPI^_BDMNx>#XQR2s`$d=r4*(G(|1L|Nc@T^NcJ`D}aGQ-(G{mwZX;AZWwAzaZg&O|cMtmh8UoyseJ*xvt~=Fta=9DS5!T>C71&Ix!88&)RW3R^E2!rFv{tT z_3+<)Wg+&eaI-D_Q4za=z%}h#pcJHpCe9eE?MfvpSKn@09WO~#v&3Q=WyZmm5mP2U#~`;UspM0)8nEQqy0?b*M(Mk7IC2ahkp4025{(!`f?mTQhTSGlJK zEatkDz5roaikeOB6D@@MzCm#Rz0E)4 z!;6eodCV zr2l{aJtJ0@m(lZVN}YLz_?wG0pR3y^MyN~aZ##==bj!ILRkz>I%83l}wYpr0U@CK5 zq*uFG)7yx6Md=`W*dTXzOd?yYl~O-@k;&~E-E;a>kzP9=7IrN82Dj^|iF1cZ?8|no z=T???*IsoTirrq;w&*zF_%?64_nQN+EA}xP=>|q|hX;bj17c^B_qSQzbirNe#VInQ z&U3$H9lZ2!Q5-oEobFvI{>6;%qZbC^oVsm6m)0 zcQeQJO~Yq(9r8uubI7VNe{@1v45gk+pRk%Q5Qhx27&U0$H%&pC6Eyg8HF7sZ<3Bhy zk$6Fx!-r5G9Sfb1TPS-aAO$0m`CO=4ulHDqo?CMFFC$5qn z)_$-2!+J*nPu9fP=eP>$-X0h2`!2|A-oRey4*sbcD3Li9%ptdlXBu~o?ArH*oP$y$nPR834(VQ4t18tt%VLlsVlq&f9m;4e-7Iv2m&ZTmG$e z`qSNJGGV5Z?N7@;>I^d9Z`U8ad@7$o24mLv&LYq4Z63QZfvnFc-*9!@VM*S4S344l zSlrI-amAm|K?cnU3_KJsv(8=;8!z|TDa{V{a&w%e35p?m>EEAQzVT9%KtQ(tlEdW3 zjsd>MR?ibdYAL}fqez}Q#zm&(9M(4;9@b^31tzx6cavrVX}w~;PIG~*NebU9PtEV5 z<5*4j=dH?0t%tGLn@rcx$mgV}8TJsC%{Dy*GyGixgKQ7(#O?t&oqh##T-)-!oQ`E$ zQZN7UNEW~7<(lPl?nH{VZTN=kFZUkKL5a0x=7_hiJvF3I{JzRiR=^KlV}ef;y^Ti{ zLnLlP)kVwbiqurpXV9&ao$ESh2P^w|dEi(rdu z{4D;xb+@CvJ8Su2b%!)(#mj5Pf5y{sq}8Zi!@Q>s6CM&g+&&XOOsS=8RoZ#m?5iTV zi&ouTD`H-1ilnBl8m5yk8RVS5<9oomLFj2wO*l)##vp;=j(O??XQIiiX+1}<|WBV(FlS?1ys2T5v#dbFFg ze6l<8UAq-g{wEi6atxzMgkZq?*-fyA(~ z9biW~t*)~;`VbSa>8CFhkXLR?1m1-0pOA?SuvvW z_5^}j@-{t?l*M>)@o4RwCkPc{Zw$VVvb=`!kxicJA(V0GUd1MdseB;g4)lxtJz#Yq zSVnF01V`CCC`=nzD4v95^n_5HaVQI&h;CP&FZ&n6^B$;_rM;9$L~w4k&>3~|SMtc@ z)3f!OI4As8oua5Ya(XLoym7#zl9p_a-d^()zcQ6qe@gX#61i zq1Yhu&ZRTZw$pFqeRzdiXzHLw&l_=iet|rAv1D(6({v`)_?71ASFy zC_`9fP;{Rjdyt{=Ms_2@!b$=v(~>fGcb07djKg95DITC|A5@Bt8-R0~`T`17^Uw8$;knKtj>|IsZ&_w;!Po_vMIIs2$_ES_;H_w+Ft z%4S~wQ}L;O#L6@+o_bx~IV28Xv`=BsAldGE_!YH(hoC4#rvbK#C2mL$@4epXoEum= zD-^T1dSwEgNFCP!Z|Me2tT>(JfU^UJpSLMBfBuh2xOzIH9B?^_7i>~|0r(>2w$NLu z089-id!mI0y3uxZ-&3fggy?d9@r8&z`^{1?!|?u&HK0j*gU80Jr(9n)7O&Saa0ns11NAmbC8s1b>$4R8FNK`L*8dgq$TqSz9p;#K>^M? zR}=ss=MS%%y**_143b`7AjAQHVpXvsGxy<~SIwpQ*mn^CrTTM3Sy_yw4QuScriN=v z%$`~Ah$U=JUtIGZaM1%|xsE^$;t26;LsXibnoxM~CmE=p`(XV1EgKO%bC?qIJ7gBj z5`c8Z{ZFl53jP7XlB3n2&n|$dTDmI;DB)4w@k1)Y#u4{g`!7zM!T41_?)R^A`YTpl zEeoYVJeW^22#xi>qo1PA*Mhwc1YLc&64sf9(d5k_0P{KTIBTNXa9zUan$2F`14#es z0mjEHZI}a)WSByg`%TC_etFa0u&SMVAJUj-`v8T(;)1pC_`KeGqEoUFqL=`dJ2Dg#?jz5=<>!+*z~Wncf}b8(pvK)>{#&KD#|ag03ji;)286aQ+mdK{H`D(+?4@8FF(cP zJ_VK~ep=?OdHyVn(k4&qJ?OXr`UbD0WffIF^(Y{_1@@Nr;4kNijcV2>Zf zR>wM%1nwR#QzAHQXibW?(r*$<-&=!qjIu5_HW@7l9>Q}*44+CyvCIcU$>PodUDfbP z+erhQCxcRd^osq!)Yd3J$O|K!qbmVC6P9S3bt+PEQU%oBqi*HJAwKeZ3uYl<Dtrr1w%%HN6l}Pjy*>}{VeowjJG*k3;pL4Uh~+wT1PfjnSXSJQ8X%x{ zoU__2vz^kkc~>GQvCKFy8DhmDmgm&`1<_3vr1d?sZK-H>>sIOzlWFCZ*%< z@H=Mhj-CFN{&b-_wk)x-dGd?3hUM_KRYCL-L-vR)q|j=+g>Z$WwD5kzK0x?;ph zaX&HO1e@;tH*$pDeE`x7eck~{_a~-)x)!C{snwt@n(wa&U~sN;YH_`2pGm>a%LlTS zHa-HQi(5L?kIa8SSg`P{-vQ$7)95qtIVpm-x=b;)5wGt%$%qm@VHSSCbzBNd+;-W! zI%KoiYA#Uhz5K*`&XiQ``gF@utg=J!6vBLKyTGK23}6?kHJxK=Efy!0{cS|AAEuZ> z{eO!_Wd6*p6cV&0;?6z57staDnU@p2$HN#<1e@{azB(TBjdq+ji{^)OzK=MTGoo)x zcg31C>es3*BNOQKe~K#^M;&aYl-A?h#HESYlPAH^i_ml>czn+b%)B}TRv`(Zdk2xJ z`8;)F+sS@B%~Y8DATI&qu7{NOC)&@j--KlZk)<49Q^=1Rs>w^d;5BD6FKHSik=@&z z^?FGXCwXLa6?+25FJ;BlfSJS@wKafmSsIj?PpTmmCu(M}Owm^?2)uxt8lWH=KwfVA zQZ@q>kqWJ~$l6^b1$tm_j<1-2Q$a#zsr-T?w6-!7wp6@V^y@O27H;BWFZ(>ig}e*x^z?-^ zv(l6Cr{p8hXv>+}Wq97vW39Ky#5h1IOi5|;y+4Rg{P3UZ$et;g|L-w(R){z=`t5fg z_tWgZC*;;uT!WK|6KrIZwm*u1gUo0ok482bnDJ3gR$7JG<))g8-pMnWff_7TdrD3A zP};UxfqH)Q>(f_%O?RL7e6EVk!0j^JD^F7AwN|_LhNnxR4&IX8OV?JRA<5kF^*D3K zj|27hV&u+5Cu0Eu0rEKCAlOb7>*d+~u$uJOxyymCT|E7>gJW4!*L!bVCG-Vhw#^dE!NN>zR zJ8UU%E*~E^Wr$|A=o|j(s|DlxyH?Z1?C#`OG2V4oTC-LcZ>$Z1dVlHdXOGokg2y;pqE-+QBADPkP9_vZ%27fQ}=yN?h!WK-F~YWHk%{->&|v< zP0+LLPs0Zts>?TuC6S#fM)^At?(W=OtQiWAyY0^bn{guaKUI@hg|B>TzUK!_M9#nZLxRkclv8iL@+3}?X>Hs%3{4Ge zp~CDLGBIZw5Z>t-+I8eVo#msAT;%-7;_*a=xg!>BbWS&0!cY3-L;Vj=sr$kP6I1&b zr!rXb>F7Ntv#a}PR&LYEyy2DnMs;n!=Swi2XTK{fPcP@ab)o9DA5=@Gt!MFaBzBJX5s-nnVKr_#+l?S8AdI6=#otpjB zcRkaTr7PBzk>eYKc-Ir~}LJ>8*}8KaUe zTw8w>^z2!EvwXbpU;01eD;1a>UHA0Ka^c$OI(ujL*{~M}ag0Wg*5u+!YrT#H^wi`U z9fB0Z+;A9B<~`qL8kDebgp&r&1*}yHrG32N72{)LG1qcmXRe?~5@PO6)2u)pNnkmM z)fj<$1*P2ogm|hhmix=Y!Py+z#SP|sUNqk^SnZO$!Phk35cr`@7VQGQx71s{JtP;o z@-4fQ`(<_Ml~DubkHlBWT7MQg1m1QD&Yuv=U!&sob@&U@CGS}3#feX3*9wo#uw$5y zo2%=A$n-~gW5lJC`;o1kdt25E+b*`e>e?RH^hr}CpK71RaGm8A4!-auP{+N2CN@-q z;!=18w+__BKY^-0UQUFVXTuWl+Y~&Cbx>mUwf67_Uo)W%N%E-Q3oH{STFhA@`1DHP zZaFMLV-T%MxTmM>N9vOZD;c{J(P`)(8P-@j>J6`{)|#ndP4vy49m3E1AcIr0dPpmVjl7dtD$sc};p#N6PDz#F@b z5N#$IJzdR`US0HZNTI1VP_bg!>y}}zs z_KGeEN1}k|-*+Xbonxu)Qs&4^;sN^03(1jmSR*R94GeM2fZV*jq>sKYw*MCPGVwDk zj);}a`Hl&R{sjq^<5W3+nXVEnMpJ)Vy7r=Tko|(>yLfO-^dLphsNbi9%JBPYLpO62 zvB_`mOhy%FP^eF~YNhTi()|XGuc zm{&6aSs|M1gMZ`?zo5K`4&1NGLS%ZnS92IYKvsUQ+~&mj#~yLjr{Jx8Gx?U<7F64C za+0wgr+(cJKgLTCd7(e~0=?DxtA#mE{`3RqN}4~QXFfoM;%drBAlOg!`|A|hHt~YP z7@FW!KolnqtWb4yEctZvr+fd6B?&# z0a*X7v&dHs!VOV?pnvLnx}2)Gbf28Iy{cJ1_*PD1`s9>^S$Gk%V*a3#nPn|7IFO7V z?3iqPGr}V-*b88@5W0t|1LA{+E7-w-Xw?uh)+xgAJvrWG@vj7uimlX*4Fm-tcT8SH zU!16-()_Of_MXm0<)@R)nok7pRqwVE)k-!s(phe<^OyVLXf&@{q46@KBA+)1K_c6$ z(muNc^uS_sJB6wZ%e%3h$Zl_@c%=4+EdMh^oc0ee@+;K4lyAv7NXzST|1Ca;nEF22 z0HjfvGJZd=Efz~{P(?hOFy#;;wRTTGuz{7+Xg6jnpW(OP1lZ?0NZCml+Q)<3d%u$A*N1TSGX7bpvP zY3Bds%pW!E{ipD^td0a0lfMj@Hy)3nAH)-(FL`@yW&y~RGNF+x+(M1Be}aO@nb{%! zebaR*+3u)#wDJdd^S!#YOH`j2@Hp;3(E`C%&YwpNAcM(b%ZIUVa$CTAq5=(O@UK{> zD(0d{SoT2{LPJPS70Yc4DdF}La!UEVmcGxMK_chMQK2te^&6Q62U2)EP%GHV|9#(0 z{(_W^{EO}Zkx2+Y54QG>m!G#dB^+;H7II~*DtD9aHI}$L;|qRwH`5o`F&5Lt&UMB9 znj?@N`^c$mc;~q3xY6`Y{2Q}X2N!4e9btM2FaXNm`JhgT#yqGWquRWhg{ z>$xs9PCKk5QG?2_?Fz19hPOyve1v?Ph+8+GIk7snYL(k^q1lcg*4oe;xF5meJ}hR8zVdFWY_j$)SBRg#NV)iD47?0 zxQ!WgeSeX?MSJfTv=?2AgYXLKC1D{G+0|h+dr!#kWJ^Y_Z3$I}i3V)t)PAlG+o1@2 z;Yi$At_`#Lo@1!DtX?0WA6h$ntkf?PUeGV|U$nqU^b2}}Cfn&L8QDiYE)Ru5$)xBz zJ^OZktq{KU(cBaE>GyUQx^W{vAzS$P>Y&L$zlsD|o8{U`1Epnb7)j(c>w#4amb^M> z1a^LchH@;8GFlndm~i4lKpe#0u3kQWlZZP$iiS+<*?z8FZ8q}~+9zclpk*lG-o|)j z`eEqqbIwQKJ~6R=>|JZrpQx?I)6SQK^dc7m@Sj^u+s1T4wGl_VRh(N)dIi;Cf8<-z zI8>7Hf$Xb`jhT{@x$XU5ov}vjNSPwow{pxT#^DWC2vzn| z_kLAm4*Z^&`b4hY+hRG?uMlunTQCvM0qx`6`DaI_gjZd_*%9AX>B;H@5Ve^4kX-3}=Pa89=`F8}B*Immj^1s}5_-n~c23g%wyI3zjKFZqXZK>e8Qf`&Fgm?=`8Z)`+nvP)${CJH>{FijN=8*i>G;y zwh1KWHfX0iXIRSw9?Y`5wWN7R04*P`l*@t0sC3V*>J?2pMK*Fj<`qwlP{GN!7;&b> z{UoL#Wu$n=E#D_i=Q8;M;+3{isl`kwP3aE|wx9N%PQt&|key@w&W2Ny!3X^0#CqCO zz1-knLcF(*JSg19`_6oX_##uA`g3+4cU_v`mEb$U_1)RIh`?amsU%E;|-+* z(uKEXQ=f{VXNq^6PKE#8$O9)+$&599C)Z;yf(JG$;ArqhI^<#k!V{=N>s3Ce)HLa< zPKUSY%iG>26*7oWGPV9Hjk|($6Ijw8e!;MBG-!Oy;Hz{e%GBM7``i7W33*;$lR!n^ zbHq~~ma-e^h;Zy!-fN~dU_3aoMIT`|(IZYhxjt<{3JiGZ^DAuu3gku=Di6;lah2V0@*DW8Ii zH(;J|Vke~gPGoL>;Y;fv#HM>t&SV-Yu_ge|+g_Vw)SY)?ogf80o>x#koA(dJeF>nI zbN-=NskKO1POW^GTvlQ_{zmi5#PLWA8T9n5KWKPx4C0(9&*{WVh>pG0JfQ$S41=Nh z(=ywU3!3OP&EMEWr*ht@@o}#>dm0PWN3xo`{vrx)^KKXCwAJp(w=C@{4H z8Um<$4#iEGgQjE|B%9z0DfmW@d5{S`6q<1iETehlvc5jl4V052&-VqBH85#OSyLLa z9-HN+zf%d`rx%h>$s(|zm3mG+FEW#{+3lTfi$093xw`XC;CeZ))w<8e8~1GKnY$aKzsdD^R*My5 zk|B&@W|7cTP>7ZQK$&j9E^^{&;-oh}|F=`5AHp8yb&FqS{#LisS5!hH10<7>r|+=d zwG2Jq0|~?L5U@pZsO*Fy2y^9?RZ+_v>$zUi>~YU`d$JeUg!LG{+sS1?_vsr7&bh8I_mlpU%lZBS^y*~!M zLGV$BS?JD;mS2+V$Sh&1kyE@LI}9deu4lgXOesF2w97x^CIAv~l{ZoK>u{VJEG`AD z!vH~!EB?vT!N?4G{ZH4#bhTvM3v<1IsKUgH4-iui@xa?_5IFuBH|sC%4LOZ&Q2R`{ z{3n;k>63!PfQr@wNQkNG((2_!mj;d5q@ORt+UTU!GI9|_@R>$Btq#*ADU1+$0G7^& zAakg^Eve)V=yYn3@YQAi^0;Az1Rv?upXLGg^zpkL%kgU21gj}us@FbW3 zVoV*Qm-V&o)j=N0B7-7sQN`{I6VQv@xXgc+WAiN^>^Gktd3l`2OyTKK(gbITZI0C^ zi2S@j3xQ)9TVY?38TTk>xJXa&2PsSPeTz2(m95K`i zT(ny=0DxU9l%~0z)57Au-sGnJUdfaMU&0y9Unn7z$icg-PIOM+p}@gXAHA-|w*(`Z zS0-0ko^G}P-raD55en>59+7iv*S+1qvZgMh_~r(B>ef~~jlBuP-bP*di#6_n%NNdO zJM2SLGjFm(iG$wi9Z;8OL6suYfw62xpfN7sYr^K7SiJ5icEyBe3L(J?IsgT}>_$KIE1 zw>B!7H0rX#h9_JH;eelX74gUX(>=uda|F zYJ={Uq=S~+A2)_}3g1@}jK^-RS16b?zU(^Ab`7*5kbqJQ!y)%1m{5O_`2=cFeb84F z$411ox;$W#sZX%h15Bh?XA2y|YmMA^=Y0?eMZyK?0^%|r;#h1>2lVM78F?j@afY~~ zZ&vH-qF#S~LYCpiwl(65VSvsJHqRAS1i+Y^3?n@v-p-eJKZeDoxpV?aNiu)QiH;ll z)GT8A&a};nUZDCHO#f74u38orrJ1`t{}c2jLjfg>P`ZHX9U^ot6x|BtMwtwzn3oh7 zU*Sy1?qmfWume_Cy+NGF*o|{0paP!(mVcg$UvnWx$uot28`=#)$PVbhLRP-9)b_uQ ziNZsss+!4HBn!5Bjk=dQ7)hPTV=eXOR<6c&N8B6LRmjtb6aU0K9(ToyCM>!u+`_B5 z80$h>1aP(1r0ECboOI&}=@z+0&AX#ccNhKI|H$RZEDZ}`Ju@S3CSR?vw{xvqnUc5~ zD^Zaj0ysi&-D3xEr2)ll5lnq$t zdKS=%v==gJn^VAMH5b!9pE|k@o=HD8 zk~zZ)M2@A#+HJ9r$-FJ2h)GK^I+Fd0{p+-2GrcnfogXqn4Q?hU_X}a$%!rm=?a6rj z$drT=gH|WX5kt<%!avJ}6#>&2Z=QVrC`hIx+T|xQ1e=!nf1a?415yr?c3aB8+@qj(d#sQE<2DY^! z80A&Yb)5!V8FWyj>z^Ztr#g=*!YC3YN?&u zI{mAF!z9bMb=QXWQXVb~AlHJjK-szkw`V5OQ)iYL5+Yur_KB{ffokd@MwC*dytC*!KHo<$P7a3WJc7wUG=} zTtziXl2}{*VGpYG;Rm}`_Z{0ggj!-7`57})O0%@__nPtKlg`3R_IQ_}k_F3fVTsJh zozeoGoVcs({i`5K^U4JZT^hJ^g&%*#$-eHSaM|s4{5_GXa$-3}sUZPZFiFZ+hLW_! z=V@ARUq{(jQ43NTwQH2?v$j757c6>Yn(kd-&5i$2t8cAi@}(piWj_MZ76kBB1mOp| zQ}H3iAS)Ind{zFfX}d$XOIO~P8%ZMgo0)8>s7}H5cO9TP^zvo=saJERWL9yln1`vY zm|5A`n6)g0^34bjHCCz7+mhXM>ueWe`hG*W%G&feW%#kzZOHa~D--=Yznr>J1!nKl zqEYQ%h4E)4J@2=iH&%H(gvAf0S(84omv!_jtZ*>$G(gRzoVvJa*RrsXl3cXe+f{`D z_hjDz@7-0Hv7N%~97I^`Yb~t67#(}v=j!|CcmJ+?jZAlyTt9xqbJn7V822eibRR6;C2!P&uyT1qMO&UOWz;h6+F@P5-xNLct5P$AaP?IaU!kAZ#h@=Lg{^_ae0} z^Ku+A7VizW970Rv4a~h$0LP?!_+dmUNT;1}aaf zP}c@0Tl7l)I`fit=cA4l$e}AJ?UJf0xaBoptjeIR&|KCBk!eGZDR-b)Y1Km8IyeWX zYy+1_s9xJPfmARSP`46=nn%EV3>KS_h6muTv}W$49qieTPs=+^mqE%-FA-SxHK)wd zLCht`GzHTql?AHta<9?Y1gH-IQ7lcg#h(p%BOuMT3_K9)fL*8{Pz`FllKOmw*UQ4k zW`H2ECcxpKcL15<1#|<(v2pvrWNh>a?u>qC?t`C{QoBF^apPT#{><;g;*>jFj`Ml*mV4)TZ@ty1a(!e(v!2FO(BJ1OdsE;rw< ziUPMV&_(_W{rGyO@tW3An1Wpf1(~fViDFWSCDX~Jk3d|kq!_RT1&O!0xo!|T0M|2B z`3$!g;L~+l_$l>(G1^OjpDJ_%;m?+P2cDa|r=-ZR&krF_yWn0CS$w|)G@etYJ~<-| z;BdXKbTt!Y!uMfvEGZY$-(x0u8^F2l>@;7kjjpJH5I4X+OlzXkx}U1hLz;BRx&yH% z%$e1~P~PLUd8?sSWUO*oDAVBmm$!PrrIX(zScW+ucj3DeU&u;cf){n|mIQ8!B9|lN zD}VW)d;`CRBpt&d;d{^4XCF z{C(nYbndg$tVv*gRy{;0=S<41QH>f&+BM8kkm1BW3}jz{I;LKY>zUhi%#O3*FVew$ z-e=iJEip|%*KENlmR`oCy#L*6zQVq&p>(I44?<0~Z{!zn>_O-G?uFb_atoYhQ7h2Y zY|;wI<=hss1TNAAed@?*If52D$iEO&K$1F;2)gph124;fl-K04CN_s)ZhWb%#unfT z%%)JM<@T0SRGVI&abs}cHswVvvbMh9ia>8ELK8p}2O%|x`!=^jyr0F%&ft-aJrd#D7{0`z>T`BRe$Ub!ca2;7Uj}wqPv3b)e$A>Pr zs}8|3CyCv)H4^VDuZXIP`@Lc5Av4(GKA7X~MbTfo7`s-E>b9y32K0uxk8E>4ovIGx zZ=Znk+mt$5Y|dgf(ulbh8BOx)v~u@7tLqpi$TY4J7y%5;?>}b&fylGa3~2sT`TvCV z7}*gSC_)NsYGZj*oEw^xIKGPcl)S>ObTVJAK~Li*DP@%n>^Y)ivA}F?E;&VGu_L5*9;kONqJf3-2rsomqGU zFsWrMXMj@e5c6m+1`DQl4s+ znUU`4{Ti88Pm9~po4*nO^{M2k<_nF>-<0e?BHUh6EKA2i%YVMzGaVfbnjsel(7ewA z1%Fnsm17&!M6qoGw9c$Xh=e>9S|L?^CCoJ%zP7O00o`pIFzD^-pRJ!3eQid$1NpTQ zpjF^4%;sEJ|A?H^G(+FOA^)yKt2#>u_% zg5)A*Nhb4tl?Tc3q=cS&l_QvPOgzR{kgfjL#{BmSxdC(GW_y-}1YXsfQR3r6JA`C{ zAAxG0U2S4q?8mTROP+wku_gQdZ$g%&$CmEz{vZrS2dgEQx|e;3Qd=0?$B24%&k5O_ zL?3LkZLyV{#9fjR9P*%HCPYdx)ttnz4(I?zwN=#Wa;>!><+{gDj~qM>rR5PD$+6>vE5gN% zwA)YLY<9QzVyN{4Lm07h%hc`#o@X$odinyQ+*DdzEyvq^oIBwJMizUqB`_v9*9u)v z!RaP9v_ee?*WVc#aYxxxCKBj$5yQA<;L&Y)%gvUwGLi?wbyRw2`B+M~WG$+{p+C3^o z_N<06E;u7T?m(skab(omGo^AGWw3mEjbpNEg?jbt<=-3c$Mrd{Ay{SZMhHt9d*;pe zK1vCv{ott=II}%>bT)&so%}VI1maf9<8B8nZ*= zDomon>NVPVVy>&cAU>k`x(|_8s`=b)3Pjp~dt|y8KL;RUKCr8ayr2tnJ>qZ7c7fAs z?R|5yh2WoOX0!*3H8Q2$#u#w6J`yA56@$RFTQ2okVC;>SoN3At?(*kdw@1=gJ%+@C z$D|1$|HO_&9~a)ABRSXdM|eY<6|thH8;JQ*Of!3j_q-&=X2-ujD7az7KmKGV*q@dfExhrj0f`mFmLDN$aVIE3kJm=_Rst}tCMkmuK zhMs#;l7}5#Wdd?Hqe#I@pQlkb{_`~kf`N-6J(>9gW0)F0Mm+GC`1v;pxy-ES`)K`} z2-eRdMC`LjOoWGuAB{^_?{n>vLTUxBZcl|P4x*P^BsUrH)go-?8#5eU}=AJBR3 z_y)Cj!N%KsO6Z4Z`Ve~Er_uH15{8BDXiIvo7f8wKvA6mxsts=pT{vxS+6f-jbPKs6 zB0i(=C2)~kn?7lL=#L@!lYNc}O|IOm5m3Gg2fri7@F;|wCUA3lFNTxov5EMFQev6M zGKUnorc~v2vcm{LB{nF&mcrK6=pWVFtCv73DXfN&Z8rQF4ITSZECZY`r+h2# zC^SjZ3IZ0JE0@^1r+Shba?=LX`0eMlL#gMevtsnmxH$_4(iiIv77vl3#;?hEJu;5} z%^JW_qSe1x>Lvfx&Tlh}yX80q=v>v+u z)H=)eDz9A0wmCl>X-C=5FNALETz+F2jrU6VN+xSvk9%B^A6A z(8Zc636Z`c)LBVFZs@nzH>Ht`wIZxYC`os*B*LqSsX#zsLD{wPbZSU}&Z>V|uR7>{Zop?E0DvpKb<5a)l*vk9X#5l!)@WC`N%oVX1FD#n3sXBS&L z(Bl{M>c}5wT6L-bBTnNGB~!6_n>q!hg?{w)s0$SJvRr1H2W98B%U34_>#6d=v`ai`@`QKgfWID`;Z*_d_Wjq1(Va(#LCXdrr@K$c1CMAdQ#6g# zpj-&g!dsIM!%#_FCIBjt6`r0W&}l2hO$K&R2p zf9-t-rG;{^8Yb)4Fxm6*S0g-P-mkI_0L`P)oM=P7O#NRAHY-^&e|weGdeqJRplwKx z%{iM+{rqM|Lou2zNSFLA-Zssr2XsSHWR6B$8Fk00l2B!Eub57Xno@jLEPMD>9EBjXV=a-Slj zcM9i?L=NKr^qDq(cuX_cV)dZvcK00_4uL6w>lp>!!QN&e?jxKFGhUF-Wvi9-_=8`| zc<5NoYTa+df+IuwDxJP+zOL*&AU_BDvI1l@9lhLjuNW1M9zD}W$tka~a!uS5b1o^; z;8=)3h8$NC=PiEbMSr4#Z)arxwbturze{1_0DKvHuH28gh$WE@l3hi!>>pJ{X(s#0uIn{jqzBq2EFQO`gG=bY>+ zZ=2}G-DD#b9vY1k{;FQI*L^-zEd?y8UmE;aChe3t{`*R8_O?<97Y%rRkM|wV2G1^k zrs`IBcz-0entSthrJHNAUh2QDE(cYREmx!N2aAqXQbh0}Kd+Vk^+4kjk)R_?`IL5J zH%>>q7a;#bA(flYm+~$`w8{C7*e8zwgV17vfg1Zt;|t!x`EE~MQ&MGeJ#X$pmcwoz zfsga`4}+s(({h6;pE%Pa&glgTFQq-cVkdpP@lubl!8Lzs28lN)Omv9D$Vn6rUjh1U zi1Y5!sfA9X3&!0Zx031ZpO0^O#M0}sP*`&ct;KP2)c%Ub`$DC+P=Ajjmp|gX@-hF1 zpI?YnsYi@~4JGk>#9BwW2*FLhs}!|585n0t+px>CPj@RXcf}AfYQ=G>Bh+3YJg4E2 zXMpzn7LE;EMfgZLeK>9gA-+XO;rh&uSQayc^5}FbfsG!;UPSBJj<%XKD2Qetn0JO zEBCiyQm#Az1>RdobkrJ(A>@TcYWe^=i$PVuyOolRE!H8M=R5of-$)z7+dKdfOU+R= zmhM#4yI~LqKN^QVC#ohoH%Zf<+6JQ6SmlWAsTI3-12a&5%IVAeh*Har zowO~BYw=j;H72yR+@!ICQ>qJ#LY|~YQ+0W<(+J<(pr~Tid?~$%SW!xUuJ78#54 z^;!e{#UrqtTaw_$N@C~ts2k%sQB&2T+`*7BUb>5_DqpE6v!bc{DlvHL;~?RFqWg#9 zrJIRnwW=HT=X3`|i>-a|Nw(6vWrbD`m!_69{C=IelwRu9WOYX$Z5!G2CgItxK3V1* zO!}rNB94~=!d|3Uy1#h%@&&du%ajeq1w)*|hP41&z?9EU+NeXK`^`R_|77Q`#jnIL zYJp~q7WdDKSV}zN)@mJwfA{jps!exauDKji!1_R6`P}kWlK4!916K$sBQsax&B1OXp83^(qfp_#-p-GD&&PA#s46 zD)X>gv-xJAd{aJZjJ5FizhTpLS^vQO^h^8%G7A}ne1C%W{F9##HsbWppfo941>e2o zvrO0iq>yt=%doGBjw;2w1R~3zVH415J#jPk-HY?=oJ?zCc|5lb^)p7 z^LdSDJ(SagYz6A@O^FpgSOb8vzy)UZ{L^DMVjHCbQyKn!WBIqw&bX1r%$`{hBWO<4 ztw%cR`ul+xwS4oAoQUjTxPLJ}_RYslvjo)Ml&AV@V)p2dP zyf&09RY)LBsMocEkd2bRGbv4zM7mp0O}vpUwJqv`LHnCQ;meqGbCwdhy-}*Fhi0tJ zA+(R7Lc)){08qDqiu3E4WtV&)hZG4MS5yXU`=~%v{{jUBtuv^-S&o-bI00bEc1i!6 z*@m7r-L$hB`hI)fYIHfee-HN|u?f8%!PTiB;oxYI_PCz{hFg)6 z!<}%-0Ts2&0D3J443|sAuTCrddk#-m0tQ*SVYy>U{c(tJaK1!PH8m8umD z$2}mFs)1rL)Me5AKET*?_VWp?071{0*<-Y;Dc(zy*Q)sPZvQx8k<^eqr-yRN9~Ibq z(tL0@bu~I1D}grbgy}G1cuuI9&1>Gcc?ZC^ow}l-GKI@isV*4Qx-T9BP8guU5G(4d zjrg~s!)T2GN)2Mr01ELb_04Sl2GAdgmZ1eW+d5gB`~5WiTLAEv1qY7AkJK+*)}T14 zv3W+<0|QhcOGVeI@w@Jd$!qx!*FZ;vr(#=ff8}{uo4~s)07fi*Y?(+s;gc8c{NLgS zJy^cgtm_cr9++{Z^K$P5_F;y7Lt@~r*n4I(f#-eM3rt@dk8$rn)H<96FQ$$FDGXQZ z);xo|_GcE12KPEHb$2N9AP|!SbAwYi%b+ITOvI%wHK;;92xh{r(+^qkDNK($k+PO(cWpsunfwR`2?X3>bsn70auKqLwvsO0<%j@ZE4*BLt8djjrZ$@)V;l^Y& z^=~D*(c3wuJ9i#9x@IbbJ|^9SQ-Jm|EiloHyz;ISifcOp-Dx*SrC2G1I@|LWJRION zHHbTbQ7mMz{b){R`Zpq_=N9t~Bm7A}%XBS%HW2|-ew zLEIw72Urv64g%=~SH7d7Mxc)ck1G=SmQ0FsH0SxILD}8GMn0;wYRQzr>tCsR@)Y%P z5)Scnre|M;OiA_EnZ5$-WPgZp(J$_aBUMh>oskFGox@kLNj+1kv(Y*au}AiXmwe;H zEnWfWe!M^cGItPnPT|sIA{=N|iK&?6A)Xy#%c>7Y-J2V83#alzpX$f1PF0OY#)N1- z(Lk;=N}7eq=h==BFBX}I<)9^eTK0rTu5tz|tcN@~*sVKIKPifH7E$dCd0boH3OaCu zdtjW}mie|wV)G#3fh!6nu%NXkxx}@1Gg*2G(B;aC-94NE%QOp$tD2xkgY3XK)~TaF z;=hAlU(h-o3~Dx~CyS0Yh)wDs7IbE}fe3ROr!%lo)MemBN_kc%N_zg$cU!C2TavyV zMzfw`F3oARFQGNH;-4@vlh9FG1e`W6rgcLEYt5~F17FXlCm7Tq#O1WiNflO8Rg|WZ zoh#c7f2FPj34;R774n74c_`Dt$~`mL_ze+1^<;Z1I`N`TOmZCX^%|NY>4mx)o5KZQ zs(*3@L>mw@~!T0l4C&3-i1qBL1y(2cyhh#N9JBff(^T<|1BGwryU| z{h(GFon{)HzR0J-{YsBWzwAspCC>Mj^I8P(9$X?|n9yhRG^}vRXqNdj`Ob4HjpV{~ zmNp{_lB*R&nZNC*h(04b{~{;E61%Rh0@+^n=3DWfDn%4>nR+Aa zpOtJky>P@%bkaM~6&*cZtwCu=>Q+i)5@C}z?saAA+nely4<8@@m_n-U?`W<&xNhrA z?V3IyPCdSQTYTmF&{X#D>rS~;a9Bz+sz+}sKl$-jghbweW5ZOl0Tz^`T3|DsLj!PN zFIbaFK$7FVkj~uoMq#+iOzOeulK_QYsWri0QDYEZ&gO?50%OPjLEN19FM7O?!D!JG zutCl@z4=|>5#gd8`R_NUDR_k8v3nYB->Ehd#i4f#-z5 zb>t1>zgJ}oLti}d$mGB90Mc%Yvls8H{X@L%0RggGKzGS<2WSfnC+Pb8`&RWJB0=N2 z>}Htbn7>an_b2ZG2XzX-i5%)%|GN%#b{h!qgX+*47W`t^>c^2kA|`;&e7LrtA+p(4 z1JDD{)B5+wZ(pncB`)}ksa+5P5u}?+@%z7?h25J(UT9hJ*X!vP4%FB);EdJ)NC-LL z$ot=0@E>0D&=?<3v9Q*oEm%*i4_oVvSGvM&z~hfr;fL>e^)#B z5eMQP$G?;Z3FL}qK#dT32$DjL!O7x15PyJ^G`Ne@dXfD4@WGnC5&#+?oqrLl@_isC zUF}h%lKK8m!TRMbiM#gzyrCB$6MO&X9zKoV1U^E5s{r1KQUW}rx?TwCxQE&M`$i`6 z7xdZsHo)f@RQ&J$9UdC#HPl+LLf*lT?Em+I$fY09bWv8L<1eg8{|N$+R2* z$bGK?ixjHhb0;y|fK|Rc3XHQ8<6}`!I~&(g4sx8WU;wom2|)qtpAPVQgBVOV3dRr` zJVeYa&`T>gQviB*u;fED`SE4InVz(GHmhxCOw+TP{W&fPFSO z2>{<~!2s9vI|Kz5S-)(B2}srCkf2ad0q<9w5ZZvSjw$eFF?!~l^^hS^>)DPYD#8KoK?2Qyw=^ z3(&8nK7x4P8FjO^c2Z9~^vMw|{R6x%1}+;XOj-wgD7c~JD~P`*@rCDQ)~NqQQE-7c z=FR+nFT7{7TTcMSYB`rj@bFLB^?wByJlz@oOGCjAzaINP zUUGPz{(a5KzsEUz0mp-Xk@xT?!v8nn^Z)50Y1_`%A`cGHuZY1e2=83?JJ|G_0f&Ir zP6K1@3s_J!$GqAH$5IYZ{z~JHJM?!tU-LPC8({L2WY-eAvw#-aRd52P*$d5_YXR?Z zfCcNe1}7tXu-%;lh%U=k#GV_K&>vY&Wr%Te272(-n)Wa3NV9IV)9A%V{V*P-<{I(+tCG7)#M(i8wH(a zfIt>sHWcaW1X#swZ>euTq3+?THHO()OFKxZ z;p<$^Up`B1_+$5S81c(K+Di6R^KX#WzrX_WyBjzX-$0$VgH$l2=>c@F7y>vbyicd9 zREZY$9pD5D6Vdq;%Ja0|gTur-&IQ?#a;Ri5rIRi5jNq6KU;caIi>X))$*#6?-$ix; zro;>Y$BZGg{BKu-^y7l)Vb9?S6lCjh^4rB<2J7{H(PyX}0Z-cb$k7X9b{zan9pGTL zhgj6r$GFoCxFidWOL;+Vz$W!_C#2B9ur9lJoC^7(+ZI?Powasq0_RKGArT7bI!wBb zP9}jn+zaH(4n#%0!1pR4f@;t42J{|burHKH4}g#MvZ#Fze0MP?r*p(vZ*X9ydM`Gt zF9LMpJjN45nbP1U$>#uk6Ir^2z!Lx+a0cjBcFwI1Eq6#VZZ5;4a#Oc8b9bzEZS`k7 zYIqG=kQ~&2R^V#Y-I3pj48Xuzi#0G!lu_)0^sSCf<8$^E;A|3E8vmxs-iiO{yBW-p z1frs6P5)p+6_9oc1inD)AS*aCxo+gls z8k9pci&u&{${qoeyKb(=N5hIl;?`okRka5|Kik*F8<_dHpQigx8fCr9b!}c%+OVZ+ zYvlK*-_qzMwnYBIlpTvFr~h<+Nr%Z z0%fi4=1Z|#L`HFTbeAs*inq-@a53`{tzC^RQZm&O)A^fL$&0C67z?d$w{MuX<+wCW z&ebejB)@_b-(zl47b;d_K;neq$;v?9c-m?k)tBp$PTt(NpsYXcXB2{m z+6TH<^n&!~GgJa0&T)t!BB4Ad@#=sa%{T#4QSGv0@3ax#`UB6ERM1$v>+Ar6H9p_q{$VUw{1M0#=fJII2hJaL zaCEsac#@o5QET)DL)M$XO2`5N6id?|$w6&7@6N7l=1i5X33zrw`NfH~@-$d@RfNyg zL4SE8$g1C#?E=J_fzOlsKXdFi{tN)Le#teL&RSiF|EfK`*asSLx^4jn6+t2f&8Q7=`cH*$w4$Lf=0`M`f}sXR%aG02bTpD$5Fxf z)oA{j;fz2~s>lWmmG#{*$_7cM^vPg=ED{AWQN9C}d|g5wwVl-GAx4ftu`u9viK(CS ztwL4)a~2b5g?@x0r&k~gdhZTU&S9}sd*3SPGS9`s0vRS+CxzZ$-}p)6-uT>y?&=$; z5ag9-d?`Z#NBx9Xq5t<`fDhN^8r&IlfyMu7hO3YzactZW?bh-F|mLI=`mrDdN+cC?!B{9HrvSP`bC@pCaHa%)R zwle;K^S!a$^Ovr|{Y%COp?-6$*Q~KIcSe)Z3I9gS>N5F2?_-?V6y1eCG+K*--wZZp zDhC^FD~VAckKDEC#?JLfG>~E%JrzY#mK6v^0Q4$2@*Ff?q}c@#vXOr)NNP6{Bo|Eo z?g6#Z-m~`k(R|7*XfYLDCZ_#}O`DJtx-_SeOc8qo^Cu_2?E*2y*c$$Z8gw1gerE%@ zEFBe+^RRd0F`jDKaka=OVz0I<`8{nzv$XQ%)iG)j6bWg~tP`b2vokrf{WI-b z)HEc>Q>zI~D>90uh@;z=!p^ty#iW(ODO&xHbCeFxHbDH2hjI?{!ecemwLcv4FKva& z?|NXn26!0N`y@>{wzY6KM|KIDJ-|#g76N94hDgs6m6yrw$46UBCqwU>03&Ra* zOVsYKoRyR#8a2jvsTo&qE@pcz4kFI@w?B?cM2ENSEH>p*T;F`58gVV|xT=_Bp@h4s zOMclo@k$V4ro3ikr49&ePH z5Q<&K$2P23e#<6_KQeDzdFeyR8`2SM^!2u^Sdq=oH8foON-7f(7I1CF!dbB89!qMNAJk-NqHA`j(mPQbr2WXt1X^geUf>aBo+bG5^y=Y+m7X zEloq?8P1!=RcXM@(z$Rpclwz->}|7P-|k60A`Y?o={351mGQROV&tjiJj}-<#L*h) z5Two88AoCT|2`ncc<-YgIh|gESoMc!n z5H{FgM$D~j$mYbPMOUPU$VoT)3&cA7f&9t9|BZ$BuP1o;n*YBmr~gmNv;Q|2+0xV5 aIam~V6SyQY)p7* Date: Wed, 8 Jul 2020 12:56:23 +0100 Subject: [PATCH 08/51] Added Origins file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e99079..3d1bd86 100755 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repository contains OpenAPI definitions for the Common API for Federated Da The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). -As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact [Aridhia Informatics](https://www.aridhia.com/contact-our-team/) for more information. +The project was [originally](./doc/Origins.md) part of an international collaboration on sharing data in clinical research. We now welcome contributions from a wider community. As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact [Aridhia Informatics](https://www.aridhia.com/contact-our-team/) for more information. ## API overview From ad9e930a9cba2ee06a303ef2fc6a32c98404d919 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 13:52:05 +0100 Subject: [PATCH 09/51] Layout --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d1bd86..2304d2c 100755 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ For maximum flexibility each section of the Common API is defined in separate su | Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | |:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| -| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | +| Level&nbps;0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level&nbps;1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level&nbps;2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | Details of each endpoint: From 3a5ea1a6b2e82e5aed4659c446945cf8f14cee94 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 13:52:39 +0100 Subject: [PATCH 10/51] Layout, typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2304d2c..8cab9e5 100755 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ For maximum flexibility each section of the Common API is defined in separate su | Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | |:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| -| Level&nbps;0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level&nbps;1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level&nbps;2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | +| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | Details of each endpoint: From cfc4d446e63074fdfe185144da441c4bc1b71de7 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 16:34:54 +0100 Subject: [PATCH 11/51] Added VERSION file - currently in WIP as 'alpha' --- VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..1c6f7de --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.0-alpha From b5e80300f97f0f2032039400cd1a142ceb5ac17c Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 20:09:26 +0100 Subject: [PATCH 12/51] Ignore R runtime files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f506c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.RData +.Rhistory From 2bd02e13433d39f4819dfde1efef666e4e1a247b Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 20:09:45 +0100 Subject: [PATCH 13/51] WIP: User guide and example query --- doc/User Guide.md | 351 +++++++++++++++++++++++++++++ src/examples/example-query.graphql | 9 + 2 files changed, 360 insertions(+) create mode 100755 doc/User Guide.md create mode 100755 src/examples/example-query.graphql diff --git a/doc/User Guide.md b/doc/User Guide.md new file mode 100755 index 0000000..146c880 --- /dev/null +++ b/doc/User Guide.md @@ -0,0 +1,351 @@ +# User Guide + +## Introduction + +The main purpose of the Federated Data Sharing Common API is to support analysis of multiple data sets while allowing a data owner (custodian, controller) to control how data is exposed to the analysis. See the [Origins](Origins.md) for some of the background to this design. + +The provides a standard interface for data sharing protocols to connect between a user or client programme and a site implementing the API. The protocol is very light weight now and clients (users) are free to call the API in any sequence but we expect the following typical sequence of events: + +1. Discovering data by inspecting **metadata** +2. Defining field selections and filters to **select** +3. Selecting data directly for centralised analysis or using federated **tasks** (computation) to process a selection +4. **Combining results** from analysing data at source to produce a final report (e.g. a chart). + +## Implementation options + +Sites are also free to implement the API as they wish but there are some conventions expected. We expect sites to implement in one of two modes: + +- Level 1: users can connect to the API and select data which can be downloaded directly. This may be suitably de-identified: + + - metadata API - implemented, externally accessible + - selection API - implemented, externally accessible + - task API - not required + +- Level 2: since data cannot be shared the selection API is not only the task API is exposed + + - metadata API - implemented, externally accessible + - selection API - implemented, only available within task protocol + - task API - implemented, externally accessible + +## Terminology + +- site - a data repository implementing the API +- client - a user's programme interacting with the API +- container - a Docker container encapsulating + +## Accessing the API + +The API is a RESTful standard web-based programming interface, and user can select whatever client language or tool that they want. We have tested using `curl`, `python` and `R` as well as graphical clients like Postman. We assume the user is familiar with programmatic access to [Web API](https://en.wikipedia.org/wiki/Web_API) endpoints. + +We assume the user has been provided credentials to obtain a bearer token for API requests. The method for providing a token is not currently part of the specification but in a typical [OAuth](https://en.wikipedia.org/wiki/OAuth) model, a user is provided client ID and client secret (password). Using those credentials, they call an API endpoint and obtain a token. This token is added as a header on subsequent calls. The token is intended to provide authentication AND authorisation. Sites are free to change the output of API calls based on what the individual user is authorised. + +Examples below are provided in `R`, `python` and `curl` in a Linux environment or similar. + +## Key payloads + +The metadata API is a read-only API to discover and navigate what data might be available at a site. The API uses specific payloads to define tasks or selections. These can be constructed programmatically or in files passed to commands and libraries. + +Selections are currently defined in [GraphQL](https://graphql.org/) and posted with `Content-type: plain-text`. This was chosen to abstact from specific query languages like SQL or RDF and to leverage a wider range of underlying data management technologies. A selection query is defined using GraphQL [queries](https://graphql.org/learn/queries/). Broadly speaking the structure for a query selection of fields `field1`, `field2` and `field3` fom the table `table_name` the GraphQL would look like: + +``` +{ + table_name { + field1 + field2 + field3 + } +} +``` + +Tasks are currently specified in a variant of the GA4GH [Task Execution Service](https://github.com/ga4gh/task-execution-schemas). We expect closer alignment by Version 1.0. A task specification is a JSON object, with a selection query embedded: + +> TODO: This payload specification is being reviewed at the time of writing. + +```json +{ + "name": "MD5 example", + "description": "Task which runs md5sum on the input file.", + "tags": { + "custom-tag": "tag-value" + }, + "inputs": [ + { + "content": "{table_name { field1 field2 field3 }}" + } + ], + "outputs" : [ + { + "url" : "/path/to/output_file", + "path" : "/mnt/output/" + } + ], + "resources" : { + "cpuCores": 1, + "ramGb": 1.0, + "diskGb": 100.0, + "preemptible": false + }, + "executors" : [ + { + "image" : "container_registry:image-aname:version_no", + "command" : ["entrypoint", "/container/input"], + "stdout" : "/mnt/logs/stdout", + "stderr" : "/mnt/logs/stderr", + "workdir": "/tmp" + } + ] +} +``` + +The structure of the JSON is: + +| Property | Specification | +|:--------------------------|:------------------------------------------------------------------| +| name | A short name for the task; does not need to be unique | +| description | A short description of the what the task is | +| inputs/content | The GraphQL selection query | +| outputs/path | Key outputs +| executors/image | The URL of a container image (in an approved registry) | +| resources | Not enforced now but estimates the compute resources for the task | + +A task can assume that inputs are provided in the `/mnt/input` folder attached to their container, wheres outputs can be written to `/mnt/output`. Logs may be delivered to `/mnt/logs`. + +## Command line - using curl and jq + +Using `curl` and `jq` on the command line is a low level way to interact with the API in shell scripts. + +We assuming the API is accessible at an endpoint `FDS_ENDPOINT`. For example, if you run the reference implementation, this will be: + +```sh +FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" +``` + +We use `curl_opts` to set some useful options. For example in the reference implementation the certificate (for `https`) is self-signed so should not be checked. Set this option: + +```sh +curl_opts="-k" +``` + +To get a token, use the endpoint provided by the site. For example, the following is an example of how to retrieve a token + +```sh +token=`curl $curl_opts --user "$API_USER:$API_PASS"\ + -d grant_type=client_credentials -X POST\ + "$FDS_ENDPOINT/auth/connect/token" | jq -r '.access_token'` +``` + +### Data Discovery + +From there get a list of datasets: +```sh +curl $curl_opts -H "Authorization: Bearer $token"\ + -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets" | jq +``` + +To pick the first dataset: +```sh +dataset_id=`curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets"\ + | jq -r '.datasets[] | .id' | head -1` +echo $dataset_id +``` + +You can then get the catalogue for that dataset... +```sh +curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets/$dataset_id/catalogue" | jq +``` + +... and then get the dictionary for that dataset - not that there may multiple 'tables' within the dataset, with individual dictionaries. +```sh +curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets/$dataset_id/dictionaries"\ + | jq -r '.dictionaries[].id' +``` + +### Data Selection + +Selection currently uses [GraphQL](https://graphql.org/) as a format for defining selections and filters on data. An example is provided in [src/examples/example-query.graphql](src/examples/example-query.graphql). This is intended as an abstraction from underlying query mechanisms such as SQL. + +The API is intended to be incremental: a user can define a query based on the data discovery stage. This query can be validated, then executed to different levels. The API may be implemented to support different levels or none (if Federated compute is the only approved method for selection and analysis). + +To validate a query such as the example query provided, we can post the JSON body: +```sh +curl $curl_opts -X POST\ + -H "Authorization: Bearer $token" -H "Accept: application/json"\ + -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ + "$FDS_ENDPOINT/selection/validate" | jq +``` + +To then select using that same query: +```sh +curl $curl_opts -X POST\ + -H "Authorization: Bearer $token" -H "Accept: application/json"\ + -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ + "$FDS_ENDPOINT/selection/select" | jq +``` +The other endpoints `/selection/beacon`, `/selection/preview`, `/selection/profile` work in the same way. + +### Tasks using federated computation + +> TODO - pending updating the task API + +## Using Python + + +Examples below follow the same pattern as those using `curl` above. The `requests` library is recommended. Use Python shell to run the commands below, Jupyter Notebook or create and execute a `.py` file. + +### Data Discovery + +Import following two libraries, define the endpoints - authentication is not needed for metadata discovery on local docker deployment +```python +import requests +import json +import os + +FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" +``` + +> In development, insecure SSL connections warnings can safely be ignored. Change `verify=False` to `True` as required. + +Make a call to the /dataset/list endpoint, then print the result dictionary: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets', verify=False) +dataset_list = r.json() +print(json.dumps(dataset_list, indent=4, sort_keys=True)) +``` + +Choose 1st dataset for further exploration: +```python +dataset_1 = dataset_list['datasets'][0]['id'] +``` + +Request first dataset catalogue by making a get call to /catalogue endpoint: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/catalogue', verify=False) +catalogue = r.json() +print(json.dumps(catalogue, indent=4, sort_keys=True)) +``` + +Request first dataset dictionary by making a get call to /dictionary endpoint: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/dictionaries', verify=False) +dictionaries = r.json() +print(json.dumps(dictionaries, indent=4, sort_keys=True)) +``` +### Data Selection + +Set the payload (Assuming you are in the top level folder of this project): +```python +file = open('./src/examples/example-query.graphql', 'r') +query = file.read() +``` +Verify if the request is valid: +```python +headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } +r = requests.post(f'{FDS_ENDPOINT}/selection/validate', data = query, headers=headers, verify=False) +valid = r.json() == 'True' +``` +The response should be the simple JSON token `"True"` or `"False"`. + +Then if the request is valid, the selection can be made: +```python +headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } +r = requests.post(f'{FDS_ENDPOINT}/selection/select', data = query, headers=headers, verify=False) +data = r.json() +``` +## Using R + +For R interacting with an API, the [`httr`](https://httr.r-lib.org/) library is recommended. This can be done interactively, or in an `.R` script. The examples below are at an R console prompt (`>`). The [tidyverse libraries](https://www.tidyverse.org/) and related libraries for processing JSON are recommended and used in the examples below. + +For these examples, R 3.6.1 was used, with the following dependencies installed: +```R + +``` + +Set up the library dependency and define the end point. +```R +library(tidyverse) +library(jsonlite) +library(httr) +library(purrr) +library(stringr) +library(readr) +library(httr) +FDS_ENDPOINT <- "https://localhost:8443/federated-data-sharing/1.1.0" +``` +If your implementation has a self-signed certificate, temporarily disable SSL warnings with: +```R +httr::set_config(httr::config(ssl_verifypeer=0L, ssl_verifyhost=0L)) +``` + +### Access tokens + +If the end point requires an acess token (note the reference implementation does not) obtain an access token. This example may vary by endpoint but with OAuth2 normally this involves the following parameters that are provided by the endpoint provider. + +- grant_type +- client_id +- client_secret + +> Handling JSON in R can be awkward so the examples below take a case-by-case approach to parsing responses from JSON to useful data structures for use in R. + +```R +> TOKEN_ENDPOINT <- 'AS PROVIDED' +> GRANT_TYPE <- 'AS PROVIDED' +> CLIENT_ID <- 'AS PROVIDED' +> CLIENT_SECRET <- 'AS PROVIDED' +> r <- POST(TOKEN_ENDPOINT, + body=list(grant_type=GRANT_TYPE), + authenticate(CLIENT_ID, CLIENT_SECRET) +> response <- content(r, 'parsed') +> access_token <- response$access_token +``` + +> Note: The access token may need to be refreshed from time to time. + +In what follows, if you have a token, add the following to `GET` and `POST` calls: +```R +add_headers(Authorization=paste('Bearer', access_token, sep=" ")) +``` +### Examples: Data Discovery + +To get the dataset list using an `httr` `GET()` call: +```R +r <- GET(paste0(FDS_ENDPOINT, '/datasets')) +resp <- content(r, 'parsed') +dataset_list <- map(resp$datasets, 'id') +``` + +To obtain the dictionary for a dataset `dataset_id`: +```R +r <- GET(paste0(FDS_ENDPOINT, '/datasets/', dataset_id, '/dictionaries')) +resp <- content(r, 'text', encoding='UTF-8') +dictionaries <- fromJSON(resp, flatten=TRUE) +``` +### Examples: Data selection + +To validate a query: +```R +r <- POST(paste0(FDS_ENDPOINT, '/selection/validate'), + body=upload_file('src/examples/example-query.graphql'), + encode='raw') +resp <- content(r, 'parsed') +validation <- resp$success +``` + +> Note: 'raw' implies plain text + +To submit the query: +```R +r <- POST(paste0(FDS_ENDPOINT, '/selection/select'), + body=upload_file('src/examples/example-query.graphql'), + add_headers('Accept'='application/json'), + encode='raw') +resp <- content(r) +``` +In this worked example, the data is accessible via `resp$data$synthetic_alzheimers_profile` + +### Tasks using federated computation + +> TODO - pending updating the task API \ No newline at end of file diff --git a/src/examples/example-query.graphql b/src/examples/example-query.graphql new file mode 100755 index 0000000..08f6664 --- /dev/null +++ b/src/examples/example-query.graphql @@ -0,0 +1,9 @@ +{ + synthetic_alzheimers_profile { + sex + age_at_inclusion + cdr_global_score_baseline + abeta_1_42_year_1 + hippocampal_volume_year_1 + } +} \ No newline at end of file From f9e9aa5494127772aab5cdde3e49d4ef0a9949ba Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 20:12:26 +0100 Subject: [PATCH 14/51] Rename file, add link in README --- README.md | 2 ++ doc/{User Guide.md => User_Guide.md} | 0 2 files changed, 2 insertions(+) rename doc/{User Guide.md => User_Guide.md} (100%) diff --git a/README.md b/README.md index 8cab9e5..f2cd829 100755 --- a/README.md +++ b/README.md @@ -49,3 +49,5 @@ Details of each endpoint: |`/selection/preview` |`POST` |Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | |`/selection/profile` |`POST` |Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | |`/health_check` |`GET` |Get a health check of the service. | + +For detailed examples, refer to the [User Guide](./doc/User_Guide.md) \ No newline at end of file diff --git a/doc/User Guide.md b/doc/User_Guide.md similarity index 100% rename from doc/User Guide.md rename to doc/User_Guide.md From 15199a156c2d85adb3bb06011d5cf4ec73ed774f Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 20:15:46 +0100 Subject: [PATCH 15/51] Consistency of headings --- doc/User_Guide.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/User_Guide.md b/doc/User_Guide.md index 146c880..9b61ec7 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -164,7 +164,7 @@ curl $curl_opts -X GET -H "Accept: application/json"\ | jq -r '.dictionaries[].id' ``` -### Data Selection +### Data selection Selection currently uses [GraphQL](https://graphql.org/) as a format for defining selections and filters on data. An example is provided in [src/examples/example-query.graphql](src/examples/example-query.graphql). This is intended as an abstraction from underlying query mechanisms such as SQL. @@ -193,7 +193,6 @@ The other endpoints `/selection/beacon`, `/selection/preview`, `/selection/profi ## Using Python - Examples below follow the same pattern as those using `curl` above. The `requests` library is recommended. Use Python shell to run the commands below, Jupyter Notebook or create and execute a `.py` file. ### Data Discovery @@ -255,6 +254,11 @@ headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } r = requests.post(f'{FDS_ENDPOINT}/selection/select', data = query, headers=headers, verify=False) data = r.json() ``` + +### Tasks using federated computation + +> TODO - pending updating the task API + ## Using R For R interacting with an API, the [`httr`](https://httr.r-lib.org/) library is recommended. This can be done interactively, or in an `.R` script. The examples below are at an R console prompt (`>`). The [tidyverse libraries](https://www.tidyverse.org/) and related libraries for processing JSON are recommended and used in the examples below. @@ -308,7 +312,7 @@ In what follows, if you have a token, add the following to `GET` and `POST` call ```R add_headers(Authorization=paste('Bearer', access_token, sep=" ")) ``` -### Examples: Data Discovery +### Data Discovery To get the dataset list using an `httr` `GET()` call: ```R @@ -323,7 +327,7 @@ r <- GET(paste0(FDS_ENDPOINT, '/datasets/', dataset_id, '/dictionaries')) resp <- content(r, 'text', encoding='UTF-8') dictionaries <- fromJSON(resp, flatten=TRUE) ``` -### Examples: Data selection +### Data selection To validate a query: ```R @@ -348,4 +352,4 @@ In this worked example, the data is accessible via `resp$data$synthetic_alzheime ### Tasks using federated computation -> TODO - pending updating the task API \ No newline at end of file +> TODO - pending updating the task API From 10c38790c3c0d3e3cb86ebea9c2866622de3a546 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Wed, 8 Jul 2020 20:18:29 +0100 Subject: [PATCH 16/51] Internal links --- doc/User_Guide.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/User_Guide.md b/doc/User_Guide.md index 9b61ec7..92a1493 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -11,6 +11,12 @@ The provides a standard interface for data sharing protocols to connect between 3. Selecting data directly for centralised analysis or using federated **tasks** (computation) to process a selection 4. **Combining results** from analysing data at source to produce a final report (e.g. a chart). +Examples are provided in: + +- [curl](#command-line---using-curl-and-jq) +- [python](#using-python) +- [R](#using-r) + ## Implementation options Sites are also free to implement the API as they wish but there are some conventions expected. We expect sites to implement in one of two modes: From 4eff282cb0763b9192c388a1c9d6adbf4ca2a111 Mon Sep 17 00:00:00 2001 From: sblowers-aridhia Date: Thu, 9 Jul 2020 16:54:20 +0100 Subject: [PATCH 17/51] resync submodules --- common-api-tasks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-api-tasks b/common-api-tasks index 2cf3d21..021cb73 160000 --- a/common-api-tasks +++ b/common-api-tasks @@ -1 +1 @@ -Subproject commit 2cf3d219a965ce40b6e110ffca895ab470c76c5a +Subproject commit 021cb7382470b7ae1a42e00c2d01a7b0484336e9 From cef7f5b3f217d85f00a2c3ca67f6972cf3f01fd1 Mon Sep 17 00:00:00 2001 From: sblowers-aridhia Date: Fri, 10 Jul 2020 11:39:07 +0100 Subject: [PATCH 18/51] synced selection api changes --- common-api-selection | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-api-selection b/common-api-selection index 315dda3..f0c6207 160000 --- a/common-api-selection +++ b/common-api-selection @@ -1 +1 @@ -Subproject commit 315dda3d8b213b4bb2f3d46d69039a2a73dde41d +Subproject commit f0c6207dcc7b05f97143957454029e4af07d3f03 From 6e615ba435ef6b9e65cd6ab0bb25b96b8dab8b3c Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Fri, 17 Jul 2020 16:13:57 +0100 Subject: [PATCH 19/51] Replaced contact information. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2cd829..83d868f 100755 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repository contains OpenAPI definitions for the Common API for Federated Da The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). -The project was [originally](./doc/Origins.md) part of an international collaboration on sharing data in clinical research. We now welcome contributions from a wider community. As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact [Aridhia Informatics](https://www.aridhia.com/contact-our-team/) for more information. +The project was [originally](./doc/Origins.md) part of an international collaboration on sharing data in clinical research. We now welcome contributions from a wider community. As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact the [maintainers of the repository](mailto:info@fds-api.org). ## API overview From 0cb1d9e6803ecfc213e6abacd6184a766a94834d Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 12 Jan 2021 19:16:45 +0000 Subject: [PATCH 20/51] Added high level endpoint information for task execution --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83d868f..43a4a84 100755 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ Details of each endpoint: |`/selection/select` |`POST` |Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | |`/selection/preview` |`POST` |Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | |`/selection/profile` |`POST` |Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | -|`/health_check` |`GET` |Get a health check of the service. | - -For detailed examples, refer to the [User Guide](./doc/User_Guide.md) \ No newline at end of file +|`/tasks/service-info` |`GET` |Get service information about the service,such as storage details, resource availability, and other documentation| +|`/tasks` |`GET` |Get a list of of tasks for the current user| +|`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| +|`/tasks/validate` |`POST` |Validate a task specification| +|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes the output of the task| +|`/tasks/{task_id}/cancel` |`POST` |Cancel a task| +|`/health_check` |`GET` |Get a health check of the service. | + +For detailed examples, refer to the [User Guide](./doc/User_Guide.md) From ecca2dcf0a546c4ad53c9e1b85ce29a0246d8ae7 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 12 Jan 2021 19:17:35 +0000 Subject: [PATCH 21/51] Added high level endpoint information for task execution --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 43a4a84..67c8a70 100755 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ Details of each endpoint: |`/tasks` |`GET` |Get a list of of tasks for the current user| |`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| |`/tasks/validate` |`POST` |Validate a task specification| -|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes the output of the task| +|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes a link to the output of the task| + |`/tasks/{task_id}/cancel` |`POST` |Cancel a task| |`/health_check` |`GET` |Get a health check of the service. | From 3c55d075aef213d87b544e8a60b89f186192536e Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 12 Jan 2021 19:17:57 +0000 Subject: [PATCH 22/51] Added high level endpoint information for task execution --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 67c8a70..30ab9e9 100755 --- a/README.md +++ b/README.md @@ -53,7 +53,6 @@ Details of each endpoint: |`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| |`/tasks/validate` |`POST` |Validate a task specification| |`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes a link to the output of the task| - |`/tasks/{task_id}/cancel` |`POST` |Cancel a task| |`/health_check` |`GET` |Get a health check of the service. | From c4a66fdb96f0a79917acf06608db1fccd1e46e68 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 13:23:27 +0000 Subject: [PATCH 23/51] Added ADDI, ICODA and Aridhia partner logos --- README.md | 10 ++++++++++ doc/addi-logo.png | Bin 0 -> 7206 bytes doc/aridhia-dre-logo.png | Bin 0 -> 5364 bytes doc/icoda-research-logo.png | Bin 0 -> 15478 bytes 4 files changed, 10 insertions(+) create mode 100755 doc/addi-logo.png create mode 100755 doc/aridhia-dre-logo.png create mode 100755 doc/icoda-research-logo.png diff --git a/README.md b/README.md index 30ab9e9..5fe089d 100755 --- a/README.md +++ b/README.md @@ -57,3 +57,13 @@ Details of each endpoint: |`/health_check` |`GET` |Get a health check of the service. | For detailed examples, refer to the [User Guide](./doc/User_Guide.md) + +## Partners + +The Common API is an open source co-development between a number of partner organisations + +[![ADDI logo](./doc/addi-logo.png "ADDI logo")](https://www.alzheimersdata.org/) +     +[![ICODA Research logo](./doc/icoda-research-logo.png "Aridhia DRE Logo")](https://www.icoda-research.org) +     +[![Aridhia DRE logo](./doc/aridhia-dre-logo.png "Aridhia DRE Logo")](https://www.aridhia.com) diff --git a/doc/addi-logo.png b/doc/addi-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..b10fe18c3a54271801c19578beeadcab004824af GIT binary patch literal 7206 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 literal 0 HcmV?d00001 diff --git a/doc/aridhia-dre-logo.png b/doc/aridhia-dre-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..673347679183fe34f0f81a8c638bd47e8c9e0b99 GIT binary patch literal 5364 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 m!B^?$SHb4 Date: Sat, 30 Jan 2021 13:27:57 +0000 Subject: [PATCH 24/51] Simple refactoring of pages --- README.md | 64 ++++++++------------------------------------- doc/API_Overview.md | 47 +++++++++++++++++++++++++++++++++ doc/Origins.md | 2 ++ doc/User_Guide.md | 2 ++ 4 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 doc/API_Overview.md diff --git a/README.md b/README.md index 5fe089d..0506d70 100755 --- a/README.md +++ b/README.md @@ -4,59 +4,10 @@ This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The API was original developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data repositories. -## Contributing - -The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). - -The project was [originally](./doc/Origins.md) part of an international collaboration on sharing data in clinical research. We now welcome contributions from a wider community. As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact the [maintainers of the repository](mailto:info@fds-api.org). - -## API overview - -The federated data sharing API provides a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. Features: - -- The API is defined Open API specifications. -- API endpoints should be authenticated using OAuth tokens (out of band for this version) -- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying - -There are three sections to the API: - -| Section | Repository | -|:------------------|:--------------------------------------------------------------------------------------| -|Metadata |[common-api-metadata](https://github.com/federated-data-sharing/common-api-metadata) | -|Selection |[common-api-selection](https://github.com/federated-data-sharing/common-api-selection) | -|Federated compute |[common-api-tasks](https://github.com/federated-data-sharing/common-api-tasks) | - -For maximum flexibility each section of the Common API is defined in separate submodules and repositories. In this way, sites can implement combinations as required or desirable in their particular setting.The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). - -| Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | -|:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| -| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | - -Details of each endpoint: - -|Endpoint |HTTP |Summary | -|:----------------------------------------------------|:------|:----------------------------------------------------------| -|`/datasets` |`GET` |Get a list of available datasets. Shows the list of all datasets available for querying. | -|`/datasets/{datasetid}` |`GET` |Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/catalogue` |`GET` |Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries` |`GET` |Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` |Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | -|`/selection/validate` |`POST` |Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | -|`/selection/beacon` |`POST` |Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | -|`/selection/select` |`POST` |Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | -|`/selection/preview` |`POST` |Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | -|`/selection/profile` |`POST` |Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | -|`/tasks/service-info` |`GET` |Get service information about the service,such as storage details, resource availability, and other documentation| -|`/tasks` |`GET` |Get a list of of tasks for the current user| -|`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| -|`/tasks/validate` |`POST` |Validate a task specification| -|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes a link to the output of the task| -|`/tasks/{task_id}/cancel` |`POST` |Cancel a task| -|`/health_check` |`GET` |Get a health check of the service. | - -For detailed examples, refer to the [User Guide](./doc/User_Guide.md) +- [API Overview](./doc/API_Overview.md) +- [User Guide](./doc/User_Guide.md) +- [Origins](./doc/Origins.md) +- [Worked Examples](https://github.com/federated-data-sharing/common-api-worked-examples) ## Partners @@ -67,3 +18,10 @@ The Common API is an open source co-development between a number of partner orga [![ICODA Research logo](./doc/icoda-research-logo.png "Aridhia DRE Logo")](https://www.icoda-research.org)      [![Aridhia DRE logo](./doc/aridhia-dre-logo.png "Aridhia DRE Logo")](https://www.aridhia.com) + +## Contributing + +The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). + +The project was [originally](./doc/Origins.md) part of an international collaboration on sharing data in clinical research. We now welcome contributions from a wider community. As more organisations are joining the effort, a new governance process will be established. In the meantime, please contact the [maintainers of the repository](mailto:info@fds-api.org). + diff --git a/doc/API_Overview.md b/doc/API_Overview.md new file mode 100644 index 0000000..0fc7f07 --- /dev/null +++ b/doc/API_Overview.md @@ -0,0 +1,47 @@ +## API overview + +> Back to the main [README](../README.md) + +The federated data sharing API provides a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. Features: + +- The API is defined Open API specifications. +- API endpoints should be authenticated using OAuth tokens (out of band for this version) +- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying + +There are three sections to the API: + +| Section | Repository | +|:------------------|:--------------------------------------------------------------------------------------| +|Metadata |[common-api-metadata](https://github.com/federated-data-sharing/common-api-metadata) | +|Selection |[common-api-selection](https://github.com/federated-data-sharing/common-api-selection) | +|Federated compute |[common-api-tasks](https://github.com/federated-data-sharing/common-api-tasks) | + +For maximum flexibility each section of the Common API is defined in separate submodules and repositories. In this way, sites can implement combinations as required or desirable in their particular setting.The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). + +| Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | +|:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| +| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | + +Details of each endpoint: + +|Endpoint |HTTP |Summary | +|:----------------------------------------------------|:------|:----------------------------------------------------------| +|`/datasets` |`GET` |Get a list of available datasets. Shows the list of all datasets available for querying. | +|`/datasets/{datasetid}` |`GET` |Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/catalogue` |`GET` |Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries` |`GET` |Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` |Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | +|`/selection/validate` |`POST` |Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | +|`/selection/beacon` |`POST` |Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | +|`/selection/select` |`POST` |Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | +|`/selection/preview` |`POST` |Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | +|`/selection/profile` |`POST` |Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | +|`/tasks/service-info` |`GET` |Get service information about the service,such as storage details, resource availability, and other documentation| +|`/tasks` |`GET` |Get a list of of tasks for the current user| +|`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| +|`/tasks/validate` |`POST` |Validate a task specification| +|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes a link to the output of the task| +|`/tasks/{task_id}/cancel` |`POST` |Cancel a task| +|`/health_check` |`GET` |Get a health check of the service. | diff --git a/doc/Origins.md b/doc/Origins.md index 8f76d5c..4192cda 100755 --- a/doc/Origins.md +++ b/doc/Origins.md @@ -1,5 +1,7 @@ # Origins - Federated Data Sharing Common API +> Back to the main [README](../README.md) + This API originated in a strawman implementation of a federated data sharing API for an international collaboration on data sharing and federated compute. The collaboration will be launched later in 2020 and more details added. This document summarises the approach that led to the Common API. ## Approach diff --git a/doc/User_Guide.md b/doc/User_Guide.md index 92a1493..e136664 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -1,5 +1,7 @@ # User Guide +> Back to the main [README](../README.md) + ## Introduction The main purpose of the Federated Data Sharing Common API is to support analysis of multiple data sets while allowing a data owner (custodian, controller) to control how data is exposed to the analysis. See the [Origins](Origins.md) for some of the background to this design. From 73304c3ad9e36688fafa47ac9c18a4444f3e6ebf Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 13:29:41 +0000 Subject: [PATCH 25/51] Fixed link to worked examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0506d70..d9dece7 100755 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repository contains OpenAPI definitions for the Common API for Federated Da - [API Overview](./doc/API_Overview.md) - [User Guide](./doc/User_Guide.md) - [Origins](./doc/Origins.md) -- [Worked Examples](https://github.com/federated-data-sharing/common-api-worked-examples) +- [Worked Examples](https://github.com/federated-data-sharing/common-api-examples) ## Partners From 13bf8f44d1c3e78d31bee800a8fb4a8be6846a7e Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 13:53:01 +0000 Subject: [PATCH 26/51] Refactor main content in README --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d9dece7..9aca8d9 100755 --- a/README.md +++ b/README.md @@ -2,12 +2,43 @@ ## Introduction -This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The API was original developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data repositories. +This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The Common API was originally developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data providers. + +## Documentation - [API Overview](./doc/API_Overview.md) - [User Guide](./doc/User_Guide.md) -- [Origins](./doc/Origins.md) - [Worked Examples](https://github.com/federated-data-sharing/common-api-examples) +- Reference implementation - Coming Soon +- [Origins](./doc/Origins.md) + +## For Data providers + +A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. + +The Common API approach allows data providers to choose how they join a collaboration network. + +- Level 0: transferring data directly for hosting to a trusted research environment (TRE) +- Level 1: providing remote access to data +- Level 2: providing a data providers and data users in biomedical reserach + +Level 0 is provided by a TRE, while data providers must implement Level 1 or Level 2 using their own infrastructure. + +Data providers are often in multiple collaborations at the same time. Investment in a Level 1 and Level 2 implementation can be repurposed for more than one network. + +> A reference implementation is being developed to facilitate the technical choices for data providers. + +## For Data users + +A researcher or group of researchers working with multiple data sources have to navigate varying access mechanisms and APIs. By working in a network with data providers that implement the Common API, they can use their favourite tools to query, compute and analyse data in a consistent and efficient way. + +The Common API allows users to: + +- Find data and detailed metadata about available data sources +- Define selections and filters on data +- Retrieve record level data (Level 1) or compute over record level data using containerised scripts (Level 2) + +Currently the API is geared at users within a research team who can program. We expect in time that graphical user interfaces will be built or adapted that take advantage of the standard and reach a wider audience more directly. ## Partners From db6c1a115e265e8fa3b982837e3b1bee9f1b1103 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:08:10 +0000 Subject: [PATCH 27/51] Updated API overview --- doc/API_Overview.md | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/doc/API_Overview.md b/doc/API_Overview.md index 0fc7f07..b3070b2 100644 --- a/doc/API_Overview.md +++ b/doc/API_Overview.md @@ -1,28 +1,52 @@ -## API overview +# API overview > Back to the main [README](../README.md) -The federated data sharing API provides a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. Features: +## Overview + +The federated data sharing Common API establishes an open standard for data platforms to participate in a open and closed data sharing networks. It speciies a set of endpoints required that provide a 'common' API to organisations wishing to participate in data sharing or federated analysis. Data sharing agreements are diverse and we need to remove barriers for data sharing amongst data controllers. This approach is intended to: + +- Clarity and transparency of the model in a complex ecosystem +- Accelerate availability of data for research +- Devolve the decision-making and governance to the appropriate level. +- Encourage convergence of existing (proprietary or niche) efforts +- Encourage an ecosystem of tools & syndication + +By adopting the API, a data provider and their network can implement “connector” layer once, join multiple networks. Our approach asks data controllers to self-select at what ‘level‘ they can join the network, mainly dependent on what they are permitted to do with data in their custody: + +| Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | +|:--------------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| +| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | +| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | + +## Open Standards + +Rather than reinventing the wheel, the Common API **adopts and adapts** existing standards efforts - The API is defined Open API specifications. - API endpoints should be authenticated using OAuth tokens (out of band for this version) -- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying +- Descriptive metadata is defined in a variant of the [W3C DCAT](https://www.w3.org/TR/vocab-dcat-2/) standard and a simple data dictionary model. +- Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying, selection and filtering +- Federated computations are defined in a variant of the [GA4GH Task Execution Service (TES) API](http://ga4gh.github.io/task-execution-schemas/) + +> Note: Field-level metadata (data dictionaries) are defined in a simple, pragmatic data model - existing partners aim to define or adopt a more robust community standard. + +## API modularity There are three sections to the API: -| Section | Repository | -|:------------------|:--------------------------------------------------------------------------------------| -|Metadata |[common-api-metadata](https://github.com/federated-data-sharing/common-api-metadata) | -|Selection |[common-api-selection](https://github.com/federated-data-sharing/common-api-selection) | -|Federated compute |[common-api-tasks](https://github.com/federated-data-sharing/common-api-tasks) | +| Section | Repository | Level 0 | Level 1 | Level 2 | +|:------------------|:--------------------------------------------------------------------------------------|---------|---------|---------| +|Metadata |[common-api-metadata](https://github.com/federated-data-sharing/common-api-metadata) | Yes | Yes | Yes | +|Selection |[common-api-selection](https://github.com/federated-data-sharing/common-api-selection) | N/A | Yes | Yes ** | +|Federated compute |[common-api-tasks](https://github.com/federated-data-sharing/common-api-tasks) | N/A | N/A | Yes | -For maximum flexibility each section of the Common API is defined in separate submodules and repositories. In this way, sites can implement combinations as required or desirable in their particular setting.The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). +> \*\* Level 2 sites must implement the selection API "behind the scenes" to provide compute tasks with the selection required. -| Mode | Metadata | Selection & Filtering of record-level data | Federated compute on record level data. | -|:---------|:-----------------------------|:----------------------------------------------------|:-------------------------------------------------------| -| Level 0 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 1 | Can be queried and retrieved | Can be queried remotely and transferred to a client | Federation not required, computation happens at client | -| Level 2 | Can be queried and retrieved | Not permitted | Containerised computations can be executed remotely with
selection query input, approved results returned | +For maximum flexibility each section of the Common API is defined in separate git submodules and repositories. In this way, sites can implement combinations as required or desirable in their particular setting.The table below illustrates how different sections of the API could be opened up to support levels of sharing between a hub and a client (such as a user in a trusted Workspace). + +## Endpoints Details of each endpoint: From 104fcc881a519649ee6bc8d1ea772331a6d08497 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:11:01 +0000 Subject: [PATCH 28/51] WIP - docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aca8d9..4566385 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Introduction -This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The Common API was originally developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data providers. +This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The Common API was developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data providers. ## Documentation From a72901c63fc0caa5cfca3ba54044f61abed187db Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:45:39 +0000 Subject: [PATCH 29/51] First refactor of user guide into modular sections --- README.md | 3 +- doc/User_Guide.md | 368 +-------------------- doc/User_Guide_Background.md | 58 ++++ doc/User_Guide_CLI.md | 89 +++++ doc/User_Guide_Key_Payloads.md | 80 +++++ doc/User_Guide_Python.md | 74 +++++ doc/User_Guide_R.md | 104 ++++++ doc/User_Guide_User_Guide_Analysis_Plan.md | 84 +++++ doc/sketch_docker.jpg | Bin 0 -> 25655 bytes doc/sketch_full.jpg | Bin 0 -> 110216 bytes doc/sketch_process.jpg | Bin 0 -> 109095 bytes 11 files changed, 508 insertions(+), 352 deletions(-) create mode 100644 doc/User_Guide_Background.md create mode 100644 doc/User_Guide_CLI.md create mode 100644 doc/User_Guide_Key_Payloads.md create mode 100644 doc/User_Guide_Python.md create mode 100644 doc/User_Guide_R.md create mode 100644 doc/User_Guide_User_Guide_Analysis_Plan.md create mode 100755 doc/sketch_docker.jpg create mode 100755 doc/sketch_full.jpg create mode 100755 doc/sketch_process.jpg diff --git a/README.md b/README.md index 4566385..135896e 100755 --- a/README.md +++ b/README.md @@ -8,10 +8,11 @@ This repository contains OpenAPI definitions for the Common API for Federated Da - [API Overview](./doc/API_Overview.md) - [User Guide](./doc/User_Guide.md) -- [Worked Examples](https://github.com/federated-data-sharing/common-api-examples) - Reference implementation - Coming Soon - [Origins](./doc/Origins.md) +A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) + ## For Data providers A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. diff --git a/doc/User_Guide.md b/doc/User_Guide.md index e136664..1f7d0f1 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -2,362 +2,28 @@ > Back to the main [README](../README.md) -## Introduction - -The main purpose of the Federated Data Sharing Common API is to support analysis of multiple data sets while allowing a data owner (custodian, controller) to control how data is exposed to the analysis. See the [Origins](Origins.md) for some of the background to this design. - -The provides a standard interface for data sharing protocols to connect between a user or client programme and a site implementing the API. The protocol is very light weight now and clients (users) are free to call the API in any sequence but we expect the following typical sequence of events: - -1. Discovering data by inspecting **metadata** -2. Defining field selections and filters to **select** -3. Selecting data directly for centralised analysis or using federated **tasks** (computation) to process a selection -4. **Combining results** from analysing data at source to produce a final report (e.g. a chart). - -Examples are provided in: - -- [curl](#command-line---using-curl-and-jq) -- [python](#using-python) -- [R](#using-r) - -## Implementation options - -Sites are also free to implement the API as they wish but there are some conventions expected. We expect sites to implement in one of two modes: - -- Level 1: users can connect to the API and select data which can be downloaded directly. This may be suitably de-identified: - - - metadata API - implemented, externally accessible - - selection API - implemented, externally accessible - - task API - not required - -- Level 2: since data cannot be shared the selection API is not only the task API is exposed - - - metadata API - implemented, externally accessible - - selection API - implemented, only available within task protocol - - task API - implemented, externally accessible - -## Terminology - -- site - a data repository implementing the API -- client - a user's programme interacting with the API -- container - a Docker container encapsulating - -## Accessing the API - -The API is a RESTful standard web-based programming interface, and user can select whatever client language or tool that they want. We have tested using `curl`, `python` and `R` as well as graphical clients like Postman. We assume the user is familiar with programmatic access to [Web API](https://en.wikipedia.org/wiki/Web_API) endpoints. - -We assume the user has been provided credentials to obtain a bearer token for API requests. The method for providing a token is not currently part of the specification but in a typical [OAuth](https://en.wikipedia.org/wiki/OAuth) model, a user is provided client ID and client secret (password). Using those credentials, they call an API endpoint and obtain a token. This token is added as a header on subsequent calls. The token is intended to provide authentication AND authorisation. Sites are free to change the output of API calls based on what the individual user is authorised. - -Examples below are provided in `R`, `python` and `curl` in a Linux environment or similar. - -## Key payloads - -The metadata API is a read-only API to discover and navigate what data might be available at a site. The API uses specific payloads to define tasks or selections. These can be constructed programmatically or in files passed to commands and libraries. - -Selections are currently defined in [GraphQL](https://graphql.org/) and posted with `Content-type: plain-text`. This was chosen to abstact from specific query languages like SQL or RDF and to leverage a wider range of underlying data management technologies. A selection query is defined using GraphQL [queries](https://graphql.org/learn/queries/). Broadly speaking the structure for a query selection of fields `field1`, `field2` and `field3` fom the table `table_name` the GraphQL would look like: - -``` -{ - table_name { - field1 - field2 - field3 - } -} -``` - -Tasks are currently specified in a variant of the GA4GH [Task Execution Service](https://github.com/ga4gh/task-execution-schemas). We expect closer alignment by Version 1.0. A task specification is a JSON object, with a selection query embedded: - -> TODO: This payload specification is being reviewed at the time of writing. - -```json -{ - "name": "MD5 example", - "description": "Task which runs md5sum on the input file.", - "tags": { - "custom-tag": "tag-value" - }, - "inputs": [ - { - "content": "{table_name { field1 field2 field3 }}" - } - ], - "outputs" : [ - { - "url" : "/path/to/output_file", - "path" : "/mnt/output/" - } - ], - "resources" : { - "cpuCores": 1, - "ramGb": 1.0, - "diskGb": 100.0, - "preemptible": false - }, - "executors" : [ - { - "image" : "container_registry:image-aname:version_no", - "command" : ["entrypoint", "/container/input"], - "stdout" : "/mnt/logs/stdout", - "stderr" : "/mnt/logs/stderr", - "workdir": "/tmp" - } - ] -} -``` - -The structure of the JSON is: - -| Property | Specification | -|:--------------------------|:------------------------------------------------------------------| -| name | A short name for the task; does not need to be unique | -| description | A short description of the what the task is | -| inputs/content | The GraphQL selection query | -| outputs/path | Key outputs -| executors/image | The URL of a container image (in an approved registry) | -| resources | Not enforced now but estimates the compute resources for the task | - -A task can assume that inputs are provided in the `/mnt/input` folder attached to their container, wheres outputs can be written to `/mnt/output`. Logs may be delivered to `/mnt/logs`. - -## Command line - using curl and jq - -Using `curl` and `jq` on the command line is a low level way to interact with the API in shell scripts. - -We assuming the API is accessible at an endpoint `FDS_ENDPOINT`. For example, if you run the reference implementation, this will be: - -```sh -FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" -``` - -We use `curl_opts` to set some useful options. For example in the reference implementation the certificate (for `https`) is self-signed so should not be checked. Set this option: - -```sh -curl_opts="-k" -``` - -To get a token, use the endpoint provided by the site. For example, the following is an example of how to retrieve a token - -```sh -token=`curl $curl_opts --user "$API_USER:$API_PASS"\ - -d grant_type=client_credentials -X POST\ - "$FDS_ENDPOINT/auth/connect/token" | jq -r '.access_token'` -``` - -### Data Discovery +## Contents -From there get a list of datasets: -```sh -curl $curl_opts -H "Authorization: Bearer $token"\ - -H "Accept: application/json"\ - "$FDS_ENDPOINT/datasets" | jq -``` +- Introduction - this file +- [Background_Concepts](./User_Guide_Background.md) +- [Analysis plans](./User_Guide_Analysis_Plan.md) in a federated setting +- [Key Payloads](./User_Guide_Key_Payloads.md) +- [Command Line](./User_Guide_CLI.md) - e.g. with `curl` +- [Python](./User_Guide_Python.md) +- [R](./User_Guide_R.md) -To pick the first dataset: -```sh -dataset_id=`curl $curl_opts -X GET -H "Accept: application/json"\ - "$FDS_ENDPOINT/datasets"\ - | jq -r '.datasets[] | .id' | head -1` -echo $dataset_id -``` +A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) -You can then get the catalogue for that dataset... -```sh -curl $curl_opts -X GET -H "Accept: application/json"\ - "$FDS_ENDPOINT/datasets/$dataset_id/catalogue" | jq -``` - -... and then get the dictionary for that dataset - not that there may multiple 'tables' within the dataset, with individual dictionaries. -```sh -curl $curl_opts -X GET -H "Accept: application/json"\ - "$FDS_ENDPOINT/datasets/$dataset_id/dictionaries"\ - | jq -r '.dictionaries[].id' -``` - -### Data selection - -Selection currently uses [GraphQL](https://graphql.org/) as a format for defining selections and filters on data. An example is provided in [src/examples/example-query.graphql](src/examples/example-query.graphql). This is intended as an abstraction from underlying query mechanisms such as SQL. - -The API is intended to be incremental: a user can define a query based on the data discovery stage. This query can be validated, then executed to different levels. The API may be implemented to support different levels or none (if Federated compute is the only approved method for selection and analysis). - -To validate a query such as the example query provided, we can post the JSON body: -```sh -curl $curl_opts -X POST\ - -H "Authorization: Bearer $token" -H "Accept: application/json"\ - -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ - "$FDS_ENDPOINT/selection/validate" | jq -``` - -To then select using that same query: -```sh -curl $curl_opts -X POST\ - -H "Authorization: Bearer $token" -H "Accept: application/json"\ - -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ - "$FDS_ENDPOINT/selection/select" | jq -``` -The other endpoints `/selection/beacon`, `/selection/preview`, `/selection/profile` work in the same way. - -### Tasks using federated computation - -> TODO - pending updating the task API - -## Using Python - -Examples below follow the same pattern as those using `curl` above. The `requests` library is recommended. Use Python shell to run the commands below, Jupyter Notebook or create and execute a `.py` file. - -### Data Discovery - -Import following two libraries, define the endpoints - authentication is not needed for metadata discovery on local docker deployment -```python -import requests -import json -import os - -FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" -``` - -> In development, insecure SSL connections warnings can safely be ignored. Change `verify=False` to `True` as required. - -Make a call to the /dataset/list endpoint, then print the result dictionary: -```python -r = requests.get(f'{FDS_ENDPOINT}/datasets', verify=False) -dataset_list = r.json() -print(json.dumps(dataset_list, indent=4, sort_keys=True)) -``` - -Choose 1st dataset for further exploration: -```python -dataset_1 = dataset_list['datasets'][0]['id'] -``` - -Request first dataset catalogue by making a get call to /catalogue endpoint: -```python -r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/catalogue', verify=False) -catalogue = r.json() -print(json.dumps(catalogue, indent=4, sort_keys=True)) -``` - -Request first dataset dictionary by making a get call to /dictionary endpoint: -```python -r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/dictionaries', verify=False) -dictionaries = r.json() -print(json.dumps(dictionaries, indent=4, sort_keys=True)) -``` -### Data Selection - -Set the payload (Assuming you are in the top level folder of this project): -```python -file = open('./src/examples/example-query.graphql', 'r') -query = file.read() -``` -Verify if the request is valid: -```python -headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } -r = requests.post(f'{FDS_ENDPOINT}/selection/validate', data = query, headers=headers, verify=False) -valid = r.json() == 'True' -``` -The response should be the simple JSON token `"True"` or `"False"`. - -Then if the request is valid, the selection can be made: -```python -headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } -r = requests.post(f'{FDS_ENDPOINT}/selection/select', data = query, headers=headers, verify=False) -data = r.json() -``` - -### Tasks using federated computation - -> TODO - pending updating the task API - -## Using R - -For R interacting with an API, the [`httr`](https://httr.r-lib.org/) library is recommended. This can be done interactively, or in an `.R` script. The examples below are at an R console prompt (`>`). The [tidyverse libraries](https://www.tidyverse.org/) and related libraries for processing JSON are recommended and used in the examples below. - -For these examples, R 3.6.1 was used, with the following dependencies installed: -```R - -``` - -Set up the library dependency and define the end point. -```R -library(tidyverse) -library(jsonlite) -library(httr) -library(purrr) -library(stringr) -library(readr) -library(httr) -FDS_ENDPOINT <- "https://localhost:8443/federated-data-sharing/1.1.0" -``` -If your implementation has a self-signed certificate, temporarily disable SSL warnings with: -```R -httr::set_config(httr::config(ssl_verifypeer=0L, ssl_verifyhost=0L)) -``` - -### Access tokens - -If the end point requires an acess token (note the reference implementation does not) obtain an access token. This example may vary by endpoint but with OAuth2 normally this involves the following parameters that are provided by the endpoint provider. - -- grant_type -- client_id -- client_secret - -> Handling JSON in R can be awkward so the examples below take a case-by-case approach to parsing responses from JSON to useful data structures for use in R. - -```R -> TOKEN_ENDPOINT <- 'AS PROVIDED' -> GRANT_TYPE <- 'AS PROVIDED' -> CLIENT_ID <- 'AS PROVIDED' -> CLIENT_SECRET <- 'AS PROVIDED' -> r <- POST(TOKEN_ENDPOINT, - body=list(grant_type=GRANT_TYPE), - authenticate(CLIENT_ID, CLIENT_SECRET) -> response <- content(r, 'parsed') -> access_token <- response$access_token -``` - -> Note: The access token may need to be refreshed from time to time. - -In what follows, if you have a token, add the following to `GET` and `POST` calls: -```R -add_headers(Authorization=paste('Bearer', access_token, sep=" ")) -``` -### Data Discovery - -To get the dataset list using an `httr` `GET()` call: -```R -r <- GET(paste0(FDS_ENDPOINT, '/datasets')) -resp <- content(r, 'parsed') -dataset_list <- map(resp$datasets, 'id') -``` - -To obtain the dictionary for a dataset `dataset_id`: -```R -r <- GET(paste0(FDS_ENDPOINT, '/datasets/', dataset_id, '/dictionaries')) -resp <- content(r, 'text', encoding='UTF-8') -dictionaries <- fromJSON(resp, flatten=TRUE) -``` -### Data selection +## Introduction -To validate a query: -```R -r <- POST(paste0(FDS_ENDPOINT, '/selection/validate'), - body=upload_file('src/examples/example-query.graphql'), - encode='raw') -resp <- content(r, 'parsed') -validation <- resp$success -``` +The main purpose of the Federated Data Sharing Common API is to support analysis of multiple data sets while allowing a data owner (custodian, controller) to control how data is exposed to the analysis. A set of APIs allow users to query metadata, select data and perform computation on data held remotely. This is intended to support a cycle where you can: -> Note: 'raw' implies plain text +- get field-level type and content metadata for remote data +- define a selection (pick fields) or filter (pick rows) you want to analyse +- execute some computation on the selection -To submit the query: -```R -r <- POST(paste0(FDS_ENDPOINT, '/selection/select'), - body=upload_file('src/examples/example-query.graphql'), - add_headers('Accept'='application/json'), - encode='raw') -resp <- content(r) -``` -In this worked example, the data is accessible via `resp$data$synthetic_alzheimers_profile` +In Level 1 federated data sharing, you may have access to select row level data whereas in Level 2 you will only be able to specify your selection and computation task and have both executed remotely. This means that your analysis plan needs to adapt to the protocol. -### Tasks using federated computation +## Next steps -> TODO - pending updating the task API +Understand the [Background concepts](./User_Guide_Background.md) to enable you to use the API effectively. \ No newline at end of file diff --git a/doc/User_Guide_Background.md b/doc/User_Guide_Background.md new file mode 100644 index 0000000..84b56c7 --- /dev/null +++ b/doc/User_Guide_Background.md @@ -0,0 +1,58 @@ +# Background Concepts + +> Back to the [User Guide](./User_Guide.md) + +## History + +Federated Analysis and data sharing is a strategy to network data platforms and users. See the [Origins](Origins.md) for some of the background to this specific design. + +## Terminology + +- site - a data repository implementing the API +- client - a user's programme interacting with the API +- container - a Docker container encapsulating + +## High-level workflow + +The Common API provides a standard interface for data sharing protocols to connect between a user or client programme and a site implementing the API. + +Once you have obtained credentials from a site, you interact using the protocol of the Common API. The protocol is very lightweight and clients (users) are free to call the API in any sequence but we expect the following typical sequence of events: + +1. Discovering data by inspecting **metadata** +2. Defining field selections and filters to **select** +3. Selecting data directly for centralised analysis or using federated **tasks** (computation) to process a selection +4. **Combining results** from analysing data at source to produce a final report (e.g. a chart). + +## Understanding Federated sites + +Sites are also free to implement the API as they wish but there are some conventions expected. We expect sites to implement in one of two modes: + +- Level 1: users can connect to the API and select data which can be downloaded directly. This may be suitably de-identified: + + - metadata API - implemented, externally accessible + - selection API - implemented, externally accessible + - task API - not required + +- Level 2: since data cannot be shared the selection API is not only the task API is exposed + + - metadata API - implemented, externally accessible + - selection API - implemented, only available within task protocol + - task API - implemented, externally accessible + +For more details, see the [API Overview](./API_Overview.md) + +> Note that in most cases you should expect the output of your computation to be "quarantined" and reviewed for disclosure risk or similar criteria by the data provider. + +## Accessing the API + +The API is a RESTful standard web-based programming interface, and user can select whatever client language or tool that they want. We have tested using `curl`, `python` and `R` as well as graphical clients like Postman. We assume the user is familiar with programmatic access to [Web API](https://en.wikipedia.org/wiki/Web_API) endpoints. + +We assume the user has been provided credentials to obtain a bearer token for API requests. The method for providing a token is not currently part of the specification but in a typical [OAuth](https://en.wikipedia.org/wiki/OAuth) model, a user is provided client ID and client secret (password). Using those credentials, they call an API endpoint and obtain a token. This token is added as a header on subsequent calls. The token is intended to provide authentication AND authorisation. Sites are free to change the output of API calls based on what the individual user is authorised. + +Examples below are provided in `R`, `python` and `curl` in a Linux environment or similar. + +> Note that for simplicity and portability, code should be developed in Linux compatible environments. + +## Next Step + +Understanding the [Key Payloads](./User_Guide_Key_Payloads.md) for API calls. \ No newline at end of file diff --git a/doc/User_Guide_CLI.md b/doc/User_Guide_CLI.md new file mode 100644 index 0000000..9b39b52 --- /dev/null +++ b/doc/User_Guide_CLI.md @@ -0,0 +1,89 @@ +# Command line - using curl and jq + +> Back to the [main page](./User_Guide.md) + +## Getting started + +Using `curl` and `jq` on the command line is a low level way to interact with the API in shell scripts. + +We assuming the API is accessible at an endpoint `FDS_ENDPOINT`. For example, if you run the reference implementation, this will be: + +```sh +FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" +``` + +We use `curl_opts` to set some useful options. For example in the reference implementation the certificate (for `https`) is self-signed so should not be checked. Set this option: + +```sh +curl_opts="-k" +``` + +To get a token, use the endpoint provided by the site. For example, the following is an example of how to retrieve a token + +```sh +token=`curl $curl_opts --user "$API_USER:$API_PASS"\ + -d grant_type=client_credentials -X POST\ + "$FDS_ENDPOINT/auth/connect/token" | jq -r '.access_token'` +``` + +## Data Discovery + +From there get a list of datasets: +```sh +curl $curl_opts -H "Authorization: Bearer $token"\ + -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets" | jq +``` + +To pick the first dataset: +```sh +dataset_id=`curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets"\ + | jq -r '.datasets[] | .id' | head -1` +echo $dataset_id +``` + +You can then get the catalogue for that dataset... +```sh +curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets/$dataset_id/catalogue" | jq +``` + +... and then get the dictionary for that dataset - not that there may multiple 'tables' within the dataset, with individual dictionaries. +```sh +curl $curl_opts -X GET -H "Accept: application/json"\ + "$FDS_ENDPOINT/datasets/$dataset_id/dictionaries"\ + | jq -r '.dictionaries[].id' +``` + +## Data selection + +Selection currently uses [GraphQL](https://graphql.org/) as a format for defining selections and filters on data. An example is provided in [src/examples/example-query.graphql](src/examples/example-query.graphql). This is intended as an abstraction from underlying query mechanisms such as SQL. + +The API is intended to be incremental: a user can define a query based on the data discovery stage. This query can be validated, then executed to different levels. The API may be implemented to support different levels or none (if Federated compute is the only approved method for selection and analysis). + +To validate a query such as the example query provided, we can post the JSON body: +```sh +curl $curl_opts -X POST\ + -H "Authorization: Bearer $token" -H "Accept: application/json"\ + -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ + "$FDS_ENDPOINT/selection/validate" | jq +``` + +To then select using that same query: +```sh +curl $curl_opts -X POST\ + -H "Authorization: Bearer $token" -H "Accept: application/json"\ + -H "Content-Type: text/plain" --data @src/examples/example-query.graphql\ + "$FDS_ENDPOINT/selection/select" | jq +``` +The other endpoints `/selection/beacon`, `/selection/preview`, `/selection/profile` work in the same way. + +### Tasks using federated computation + +> TODO - pending updating the task API + +## Next steps + +- Back to the [main page](./User_Guide.md) +- Look at some [Worked examples](https://github.com/federated-data-sharing/common-api-examples) diff --git a/doc/User_Guide_Key_Payloads.md b/doc/User_Guide_Key_Payloads.md new file mode 100644 index 0000000..7569c35 --- /dev/null +++ b/doc/User_Guide_Key_Payloads.md @@ -0,0 +1,80 @@ +## Key payloads + +> Back to the [User Guide](./User_Guide.md) + +The metadata API is a read-only API to discover and navigate what data might be available at a site. The API uses specific payloads to define tasks or selections. These can be constructed programmatically or in files passed to commands and libraries. + +Selections are currently defined in [GraphQL](https://graphql.org/) and posted with `Content-type: plain-text`. This was chosen to abstact from specific query languages like SQL or RDF and to leverage a wider range of underlying data management technologies. A selection query is defined using GraphQL [queries](https://graphql.org/learn/queries/). Broadly speaking the structure for a query selection of fields `field1`, `field2` and `field3` fom the table `table_name` the GraphQL would look like: + +``` +{ + table_name { + field1 + field2 + field3 + } +} +``` + +Tasks are currently specified in a variant of the GA4GH [Task Execution Service](https://github.com/ga4gh/task-execution-schemas). We expect closer alignment by Version 1.0. A task specification is a JSON object, with a selection query embedded: + +> TODO: This payload specification is being reviewed at the time of writing. + +```json +{ + "name": "MD5 example", + "description": "Task which runs md5sum on the input file.", + "tags": { + "custom-tag": "tag-value" + }, + "inputs": [ + { + "content": "{table_name { field1 field2 field3 }}" + } + ], + "outputs" : [ + { + "url" : "/path/to/output_file", + "path" : "/mnt/output/" + } + ], + "resources" : { + "cpuCores": 1, + "ramGb": 1.0, + "diskGb": 100.0, + "preemptible": false + }, + "executors" : [ + { + "image" : "container_registry:image-aname:version_no", + "command" : ["entrypoint", "/container/input"], + "stdout" : "/mnt/logs/stdout", + "stderr" : "/mnt/logs/stderr", + "workdir": "/tmp" + } + ] +} +``` + +The structure of the JSON is: + +| Property | Specification | +|:--------------------------|:------------------------------------------------------------------| +| name | A short name for the task; does not need to be unique | +| description | A short description of the what the task is | +| inputs/content | The GraphQL selection query | +| outputs/path | Key outputs +| executors/image | The URL of a container image (in an approved registry) | +| resources | Not enforced now but estimates the compute resources for the task | + +A task can assume that inputs are provided in the `/mnt/input` folder attached to their container, wheres outputs can be written to `/mnt/output`. Logs may be delivered to `/mnt/logs`. + +## Next Step + +Understanding [Analysis plans](./User_Guide_Analysis_Plan.md) in a federated setting + +Try the API using: + +- [Command Line](./User_Guide_CLI.md) tools - e.g. with `curl` +- [Python](./User_Guide_Python.md) +- [R](./User_Guide_R.md) diff --git a/doc/User_Guide_Python.md b/doc/User_Guide_Python.md new file mode 100644 index 0000000..7704fef --- /dev/null +++ b/doc/User_Guide_Python.md @@ -0,0 +1,74 @@ +# Using Python + +> Back to the [main page](./User_Guide.md) + +Examples below follow the same pattern as those using [`curl`](./User_Guide_CLI.md). The `requests` library is recommended. Use Python shell to run the commands below, Jupyter Notebook or create and execute a `.py` file. + +## Data Discovery + +Import following two libraries, define the endpoints - authentication is not needed for metadata discovery on local docker deployment +```python +import requests +import json +import os + +FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" +``` + +> In development, insecure SSL connections warnings can safely be ignored. Change `verify=False` to `True` as required. + +Make a call to the /dataset/list endpoint, then print the result dictionary: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets', verify=False) +dataset_list = r.json() +print(json.dumps(dataset_list, indent=4, sort_keys=True)) +``` + +Choose 1st dataset for further exploration: +```python +dataset_1 = dataset_list['datasets'][0]['id'] +``` + +Request first dataset catalogue by making a get call to /catalogue endpoint: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/catalogue', verify=False) +catalogue = r.json() +print(json.dumps(catalogue, indent=4, sort_keys=True)) +``` + +Request first dataset dictionary by making a get call to /dictionary endpoint: +```python +r = requests.get(f'{FDS_ENDPOINT}/datasets/{dataset_1}/dictionaries', verify=False) +dictionaries = r.json() +print(json.dumps(dictionaries, indent=4, sort_keys=True)) +``` +## Data Selection + +Set the payload (Assuming you are in the top level folder of this project): +```python +file = open('./src/examples/example-query.graphql', 'r') +query = file.read() +``` +Verify if the request is valid: +```python +headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } +r = requests.post(f'{FDS_ENDPOINT}/selection/validate', data = query, headers=headers, verify=False) +valid = r.json() == 'True' +``` +The response should be the simple JSON token `"True"` or `"False"`. + +Then if the request is valid, the selection can be made: +```python +headers = {'Content-type': 'plain/text', 'Accept': 'application/json' } +r = requests.post(f'{FDS_ENDPOINT}/selection/select', data = query, headers=headers, verify=False) +data = r.json() +``` + +## Tasks using federated computation + +> TODO - pending updating the task API + +## Next steps + +- Back to the [main page](./User_Guide.md) +- Look at some [Worked examples](https://github.com/federated-data-sharing/common-api-examples) diff --git a/doc/User_Guide_R.md b/doc/User_Guide_R.md new file mode 100644 index 0000000..016ce7b --- /dev/null +++ b/doc/User_Guide_R.md @@ -0,0 +1,104 @@ +# Using R + +> Back to the [main page](./User_Guide.md) + +## Getting Started + +For R interacting with an API, the [`httr`](https://httr.r-lib.org/) library is recommended. This can be done interactively, or in an `.R` script. The examples below are at an R console prompt (`>`). The [tidyverse libraries](https://www.tidyverse.org/) and related libraries for processing JSON are recommended and used in the examples below. + +For these examples, R 3.6.1 was used, with the following dependencies installed: + +- [tidyverse](https://www.tidyverse.org/) including purrr, stringr and readr +- [jsonlite](https://www.rdocumentation.org/packages/jsonlite) +- [httr](https://httr.r-lib.org/) + +Set up the library dependency and define the end point. +```R +library(tidyverse) +library(jsonlite) +library(httr) +library(purrr) +library(stringr) +library(readr) +FDS_ENDPOINT <- "https://localhost:8443/federated-data-sharing/1.1.0" +``` +If your implementation has a self-signed certificate, temporarily disable SSL warnings with: +```R +httr::set_config(httr::config(ssl_verifypeer=0L, ssl_verifyhost=0L)) +``` + +## Access tokens + +If the end point requires an acess token (note the reference implementation does not) obtain an access token. This example may vary by endpoint but with OAuth2 normally this involves the following parameters that are provided by the endpoint provider. + +- grant_type +- client_id +- client_secret + +> Handling JSON in R can be awkward so the examples below take a case-by-case approach to parsing responses from JSON to useful data structures for use in R. + +```R +> TOKEN_ENDPOINT <- 'AS PROVIDED' +> GRANT_TYPE <- 'AS PROVIDED' +> CLIENT_ID <- 'AS PROVIDED' +> CLIENT_SECRET <- 'AS PROVIDED' +> r <- POST(TOKEN_ENDPOINT, + body=list(grant_type=GRANT_TYPE), + authenticate(CLIENT_ID, CLIENT_SECRET) +> response <- content(r, 'parsed') +> access_token <- response$access_token +``` + +> Note: The access token may need to be refreshed from time to time. + +In what follows, if you have a token, add the following to `GET` and `POST` calls: +```R +add_headers(Authorization=paste('Bearer', access_token, sep=" ")) +``` +## Data Discovery + +To get the dataset list using an `httr` `GET()` call: +```R +r <- GET(paste0(FDS_ENDPOINT, '/datasets')) +resp <- content(r, 'parsed') +dataset_list <- map(resp$datasets, 'id') +``` + +To obtain the dictionary for a dataset `dataset_id`: +```R +r <- GET(paste0(FDS_ENDPOINT, '/datasets/', dataset_id, '/dictionaries')) +resp <- content(r, 'text', encoding='UTF-8') +dictionaries <- fromJSON(resp, flatten=TRUE) +``` + +## Data selection + +To validate a query: +```R +r <- POST(paste0(FDS_ENDPOINT, '/selection/validate'), + body=upload_file('src/examples/example-query.graphql'), + encode='raw') +resp <- content(r, 'parsed') +validation <- resp$success +``` + +> Note: 'raw' implies plain text + +To submit the query: +```R +r <- POST(paste0(FDS_ENDPOINT, '/selection/select'), + body=upload_file('src/examples/example-query.graphql'), + add_headers('Accept'='application/json'), + encode='raw') +resp <- content(r) +``` +In this worked example, the data is accessible via `resp$data$synthetic_alzheimers_profile` + +## Tasks using federated computation + +> TODO - pending updating the task API + +## Next steps + +- Back to the [main page](./User_Guide.md) +- Look at some [Worked examples](https://github.com/federated-data-sharing/common-api-examples) diff --git a/doc/User_Guide_User_Guide_Analysis_Plan.md b/doc/User_Guide_User_Guide_Analysis_Plan.md new file mode 100644 index 0000000..3f051bd --- /dev/null +++ b/doc/User_Guide_User_Guide_Analysis_Plan.md @@ -0,0 +1,84 @@ +# Analysis plans in a federated settings + +> Back to the [main page](./User_Guide.md) + +## Getting started + +As a scientist, statistician or data scientist, you may be used to developing scripts working directly with data - your `R` or `python` scripts can load the data directly, or maybe you're using a spreadsheet package or a statistical tool which wraps up the analysis plan for you. If you're using federated analysis this may not be an option for you - the data is held remotely. Your interaction with the data will be via the programming interface ("API") or a tool that uses it. Whereas you would normally expect a high degree of iteration (or "trial and error") when working with local data, you need to plan for a different form of iteration. + +Data platforms that implement the Common API commit to helping reduce the friction of remote access in a number of ways intended to help you: + +- Implementing a *standard* API, which means you don't have to keep learning new ways of getting metadata or processing their data +- Providing detailed and up-to-date metadata on data they share through the API, giving you enough detail to adapt your analysis to what data is really there +- Letting you run *your* code on the data through the use of [docker containers](https://www.docker.com/resources/what-container) +- Supporting iterative sessions - where you can run your analysis as often as needed to answer your research questions (within some fair usage limits) + +In return, you should revisit your analysis plan and structure it to the remote arrangement. You may want to consider: + +- whether you will be interacting with one or more than one remote site (also known as a "node") +- whether you will be interacting with a mix of nodes at different levels of sharing (also known as "Level 0", "Level 1" or "Level 2" nodes) +- setting a number of stages or phases for your analysis in order to get early feedback on the process and build trust in the data and your connection with the remote sites. +- whether integrating data from multiple sources is important for your analysis algorithm (for example to develop a machine learning model) + +Examples of staging or phasing analysis might reflect a standard research or data science life cycle: + +- exploratory analysis: systematically validating, summarising or exploring the remote data early on in orde +- quality checks, outlier analysis +- sampling +- visualisation +- data engineering or integration (or at least creating a standard data frame for analysis) +- statistical tests +- modelling data +- validating models + +A modular and composable approach to your analysis code will allow you to iterate at each stage and if necessary combine the analysis into reproducible process. This should reduce frustrations with not having direct access to data. + +## Defining a selection + +All remote sites must provide field level metadata for data you have access to. This allows you to define a selection query. In a simple example this could define: + +- what table you want to read from +- what fields within the table you want to analyse + +Selections are defined using a standard called [GraphQL](https://graphql.org/). With this you can express a selection in relatively simple terms. + +For example, we can imagine a table called `virtual_cohort` with fields that include `family_history` (of a disease like Alzheimer's), `age` (in years) and `apoe` (a biomarker for the disease). If you just wanted to to analyse `age` and `apoe` the selection would look like this: +``` +{ + virtual_cohort { + age + apoe + } +} +``` +More details of how define GraphQL queries can be found on the [community pages](https://graphql.org/learn/) - bear in mind for this use case, we only need to know about selection *queries* and can ignore schemas and mutations. + +## Containerising a script for federated compute + +In order to execute a remote computation task, your analysis code must be packaged up in a docker container. + +> For simplicity we use the word "script" for this code as this is common data science but your could be anything that is containerised including complex programmes with library or package dependencies all encapsulated within the container. + +In order to containerise the script, it is recommended to go through the following steps: + +1. Run the script on dummy or synthetic data on the command line on your local machine +2. Package up the script in a container and run using local docker installation, via command line on your local machine +3. Run the containerised script on one or more remote site via Federated Data Sharing API + +![Developing a containerised script](./sketch_process.jpg) + +A set of conventions set out how you should expect inputs to your containers or outputs of your computation to be handled. In the base, simple case your script should *read* one or more input CSV files corresponding to your selection query from a specified folder (`/mnt/input`) which may be read-only. Your script is then able to *write* one or more files to a specified folder (`/mnt/ouput`). + +![Reading and writing from the container](./sketch_docker.jpg) + +When running a federated task in a more complex scenario, the same container might be used across multiple sites, or a selection filter might process only some of the data available at a given site. When combining data, it is useful to consider what 'post-processing' produces the final output you require: + +![Overview](./sketch_full.jpg) + +## Next steps + +Try the API using: + +- [Command Line](./User_Guide_CLI.md) tools - e.g. with `curl` +- [Python](./User_Guide_Python.md) +- [R](./User_Guide_R.md) diff --git a/doc/sketch_docker.jpg b/doc/sketch_docker.jpg new file mode 100755 index 0000000000000000000000000000000000000000..73adc04b00c60b1394cdb0bda62a8ee7a9773622 GIT binary patch literal 25655 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�_6K*(c(+9RrWt*MT2ohqotv@$2>`OC?|2)vZF8(~y?6I+*c|x6EdBP#+ z{^qYdA;Ja}oIz0pJ4K%sorR!oil&T~(VcFwN@9Jx{TeEG04`ft_AsNty{&FvXAg6F z?B^i|cFX56=iaz$15#w*X~xeK8LZ$AVy@*EvI|=RoyH)>A$|R3U{xk=qdxZ>^lQ(3 zQ{!9-Tx%ZKbNs(Bb>AgeJcUUN!vF_^%O0>FvQ{(SpnkX4|HMEa6`_(a>#CO=D0r

(|*I}6@`3pwf?oTVq+I* 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 literal 0 HcmV?d00001 diff --git a/doc/sketch_full.jpg b/doc/sketch_full.jpg new file mode 100755 index 0000000000000000000000000000000000000000..fcdb70f18b0a39ee5e943d9da31386656ca87e77 GIT binary patch literal 110216 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�Lehf4Z7USKHUs=e->O4L+=v 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 Af&c&j literal 0 HcmV?d00001 diff --git a/doc/sketch_process.jpg b/doc/sketch_process.jpg new file mode 100755 index 0000000000000000000000000000000000000000..ae6c4b3d5e470710feec5f72e8e3201356ec648b GIT binary patch literal 109095 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 literal 0 HcmV?d00001 From 05a49ea404bc509a3ed99cab87730c5c923ebec5 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:47:38 +0000 Subject: [PATCH 30/51] First refactor of user guide into modular sections --- doc/User_Guide.md | 2 +- ...User_Guide_Analysis_Plan.md => User_Guide_Analysis_Plans.md} | 0 doc/User_Guide_Key_Payloads.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename doc/{User_Guide_User_Guide_Analysis_Plan.md => User_Guide_Analysis_Plans.md} (100%) diff --git a/doc/User_Guide.md b/doc/User_Guide.md index 1f7d0f1..3f66e03 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -6,7 +6,7 @@ - Introduction - this file - [Background_Concepts](./User_Guide_Background.md) -- [Analysis plans](./User_Guide_Analysis_Plan.md) in a federated setting +- Understanding [Analysis plans](./User_Guide_Analysis_Plans.md) in a federated setting - [Key Payloads](./User_Guide_Key_Payloads.md) - [Command Line](./User_Guide_CLI.md) - e.g. with `curl` - [Python](./User_Guide_Python.md) diff --git a/doc/User_Guide_User_Guide_Analysis_Plan.md b/doc/User_Guide_Analysis_Plans.md similarity index 100% rename from doc/User_Guide_User_Guide_Analysis_Plan.md rename to doc/User_Guide_Analysis_Plans.md diff --git a/doc/User_Guide_Key_Payloads.md b/doc/User_Guide_Key_Payloads.md index 7569c35..a0fcfe7 100644 --- a/doc/User_Guide_Key_Payloads.md +++ b/doc/User_Guide_Key_Payloads.md @@ -71,7 +71,7 @@ A task can assume that inputs are provided in the `/mnt/input` folder attached t ## Next Step -Understanding [Analysis plans](./User_Guide_Analysis_Plan.md) in a federated setting +Understanding [Analysis plans](./User_Guide_Analysis_Plans.md) in a federated setting Try the API using: From 6d5e3d53ce52fe4a68afa8fbbadbf61597d0d90e Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:52:04 +0000 Subject: [PATCH 31/51] Split containerising page --- doc/User_Guide.md | 1 + doc/User_Guide_Analysis_Plans.md | 26 +------------------ doc/User_Guide_Background.md | 4 +-- doc/User_Guide_Containerising_Tasks.md | 35 ++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 doc/User_Guide_Containerising_Tasks.md diff --git a/doc/User_Guide.md b/doc/User_Guide.md index 3f66e03..216e2bb 100755 --- a/doc/User_Guide.md +++ b/doc/User_Guide.md @@ -7,6 +7,7 @@ - Introduction - this file - [Background_Concepts](./User_Guide_Background.md) - Understanding [Analysis plans](./User_Guide_Analysis_Plans.md) in a federated setting +- [Containerising scripts](./User_Guide_Containerising_Tasks.md) - [Key Payloads](./User_Guide_Key_Payloads.md) - [Command Line](./User_Guide_CLI.md) - e.g. with `curl` - [Python](./User_Guide_Python.md) diff --git a/doc/User_Guide_Analysis_Plans.md b/doc/User_Guide_Analysis_Plans.md index 3f051bd..8c7d6ca 100644 --- a/doc/User_Guide_Analysis_Plans.md +++ b/doc/User_Guide_Analysis_Plans.md @@ -53,32 +53,8 @@ For example, we can imagine a table called `virtual_cohort` with fields that inc ``` More details of how define GraphQL queries can be found on the [community pages](https://graphql.org/learn/) - bear in mind for this use case, we only need to know about selection *queries* and can ignore schemas and mutations. -## Containerising a script for federated compute -In order to execute a remote computation task, your analysis code must be packaged up in a docker container. - -> For simplicity we use the word "script" for this code as this is common data science but your could be anything that is containerised including complex programmes with library or package dependencies all encapsulated within the container. - -In order to containerise the script, it is recommended to go through the following steps: - -1. Run the script on dummy or synthetic data on the command line on your local machine -2. Package up the script in a container and run using local docker installation, via command line on your local machine -3. Run the containerised script on one or more remote site via Federated Data Sharing API - -![Developing a containerised script](./sketch_process.jpg) - -A set of conventions set out how you should expect inputs to your containers or outputs of your computation to be handled. In the base, simple case your script should *read* one or more input CSV files corresponding to your selection query from a specified folder (`/mnt/input`) which may be read-only. Your script is then able to *write* one or more files to a specified folder (`/mnt/ouput`). - -![Reading and writing from the container](./sketch_docker.jpg) - -When running a federated task in a more complex scenario, the same container might be used across multiple sites, or a selection filter might process only some of the data available at a given site. When combining data, it is useful to consider what 'post-processing' produces the final output you require: - -![Overview](./sketch_full.jpg) ## Next steps -Try the API using: - -- [Command Line](./User_Guide_CLI.md) tools - e.g. with `curl` -- [Python](./User_Guide_Python.md) -- [R](./User_Guide_R.md) +Understand [Containerising scripts](./User_Guide_Containerising_Tasks.md) \ No newline at end of file diff --git a/doc/User_Guide_Background.md b/doc/User_Guide_Background.md index 84b56c7..25cc8bb 100644 --- a/doc/User_Guide_Background.md +++ b/doc/User_Guide_Background.md @@ -1,6 +1,6 @@ # Background Concepts -> Back to the [User Guide](./User_Guide.md) +> Back to the [main page](./User_Guide.md) ## History @@ -53,6 +53,6 @@ Examples below are provided in `R`, `python` and `curl` in a Linux environment o > Note that for simplicity and portability, code should be developed in Linux compatible environments. -## Next Step +## Next Steps Understanding the [Key Payloads](./User_Guide_Key_Payloads.md) for API calls. \ No newline at end of file diff --git a/doc/User_Guide_Containerising_Tasks.md b/doc/User_Guide_Containerising_Tasks.md new file mode 100644 index 0000000..83bfe40 --- /dev/null +++ b/doc/User_Guide_Containerising_Tasks.md @@ -0,0 +1,35 @@ +# Containerising a script as a federated compute task + +> Back to the [main page](./User_Guide.md) + +## Getting started + +In order to execute a remote computation task, your analysis code must be packaged up in a docker container. + +> For simplicity we use the word "script" for this code as this is common data science but your could be anything that is containerised including complex programmes with library or package dependencies all encapsulated within the container. + +## Workflow + +In order to containerise the script, it is recommended to go through the following steps: + +1. Run the script on dummy or synthetic data on the command line on your local machine +2. Package up the script in a container and run using local docker installation, via command line on your local machine +3. Run the containerised script on one or more remote site via Federated Data Sharing API + +![Developing a containerised script](./sketch_process.jpg) + +A set of conventions set out how you should expect inputs to your containers or outputs of your computation to be handled. In the base, simple case your script should *read* one or more input CSV files corresponding to your selection query from a specified folder (`/mnt/input`) which may be read-only. Your script is then able to *write* one or more files to a specified folder (`/mnt/ouput`). + +![Reading and writing from the container](./sketch_docker.jpg) + +When running a federated task in a more complex scenario, the same container might be used across multiple sites, or a selection filter might process only some of the data available at a given site. When combining data, it is useful to consider what 'post-processing' produces the final output you require: + +![Overview](./sketch_full.jpg) + +## Next steps + +Try the API using: + +- [Command Line](./User_Guide_CLI.md) tools - e.g. with `curl` +- [Python](./User_Guide_Python.md) +- [R](./User_Guide_R.md) From a93ce51b9aac3a22dfb0a5af86e5c2cac3efcf56 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 14:57:57 +0000 Subject: [PATCH 32/51] Analysis plan documentation --- doc/User_Guide_Analysis_Plans.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/User_Guide_Analysis_Plans.md b/doc/User_Guide_Analysis_Plans.md index 8c7d6ca..e8aaa9b 100644 --- a/doc/User_Guide_Analysis_Plans.md +++ b/doc/User_Guide_Analysis_Plans.md @@ -1,4 +1,4 @@ -# Analysis plans in a federated settings +# Analysis plans in a federated setting > Back to the [main page](./User_Guide.md) @@ -6,6 +6,14 @@ As a scientist, statistician or data scientist, you may be used to developing scripts working directly with data - your `R` or `python` scripts can load the data directly, or maybe you're using a spreadsheet package or a statistical tool which wraps up the analysis plan for you. If you're using federated analysis this may not be an option for you - the data is held remotely. Your interaction with the data will be via the programming interface ("API") or a tool that uses it. Whereas you would normally expect a high degree of iteration (or "trial and error") when working with local data, you need to plan for a different form of iteration. +## Trade-offs + +Most existing clinical research and statistics as well as the supporting libraries, assumes data is adjacent to scripts. You will be using a federated model because data providers cannot share data directly with you. As a result, in a federated setting, your analysis plan will need to be adapted in a few ways: + +- Adopt a “scatter/gather” approach across partitioned, separate data silos +- Package up some of the analysis into Docker containers and queries +- Develop scripts to orchestrate the whole thing for reproducibility + Data platforms that implement the Common API commit to helping reduce the friction of remote access in a number of ways intended to help you: - Implementing a *standard* API, which means you don't have to keep learning new ways of getting metadata or processing their data @@ -20,6 +28,10 @@ In return, you should revisit your analysis plan and structure it to the remote - setting a number of stages or phases for your analysis in order to get early feedback on the process and build trust in the data and your connection with the remote sites. - whether integrating data from multiple sources is important for your analysis algorithm (for example to develop a machine learning model) +> Over time, we expect community efforts to provide standard modules and distributed versions of algorithms. See [Worked Examples](https://github.com/federated-data-sharing/common-api-examples) for some starting points. + +## What can you run in a federated model? + Examples of staging or phasing analysis might reflect a standard research or data science life cycle: - exploratory analysis: systematically validating, summarising or exploring the remote data early on in orde From 10894d63b8382449ab3dee507bce687baf7c68ee Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sat, 30 Jan 2021 15:18:50 +0000 Subject: [PATCH 33/51] Add basic payload, result details --- doc/API_Overview.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/API_Overview.md b/doc/API_Overview.md index b3070b2..444c31f 100644 --- a/doc/API_Overview.md +++ b/doc/API_Overview.md @@ -50,22 +50,22 @@ For maximum flexibility each section of the Common API is defined in separate gi Details of each endpoint: -|Endpoint |HTTP |Summary | -|:----------------------------------------------------|:------|:----------------------------------------------------------| -|`/datasets` |`GET` |Get a list of available datasets. Shows the list of all datasets available for querying. | -|`/datasets/{datasetid}` |`GET` |Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/catalogue` |`GET` |Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries` |`GET` |Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` |Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | -|`/selection/validate` |`POST` |Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | -|`/selection/beacon` |`POST` |Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | -|`/selection/select` |`POST` |Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | -|`/selection/preview` |`POST` |Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | -|`/selection/profile` |`POST` |Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | -|`/tasks/service-info` |`GET` |Get service information about the service,such as storage details, resource availability, and other documentation| -|`/tasks` |`GET` |Get a list of of tasks for the current user| -|`/tasks` |`POST` |Create a new task using a task specification (links a selection query and containerised computation task)| -|`/tasks/validate` |`POST` |Validate a task specification| -|`/tasks/{task_id}` |`GET` |Get task details including status. If available, includes a link to the output of the task| -|`/tasks/{task_id}/cancel` |`POST` |Cancel a task| -|`/health_check` |`GET` |Get a health check of the service. | +|Endpoint |HTTP | Payload | Result | Summary | +|:----------------------------------------------------|:------|:------------|:----------|:----------------------------------------------------------| +|`/datasets` |`GET` | N/A | JSON | Get a list of available datasets. Shows the list of all datasets available for querying. | +|`/datasets/{datasetid}` |`GET` | N/A | JSON | Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/catalogue` |`GET` | N/A | DCAT JSON | Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries` |`GET` | N/A | Dictionary JSON | Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` | N/A | Dictionary JSON | Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | +|`/selection/validate` |`POST` | GraphQL | JSON | Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | +|`/selection/beacon` |`POST` | GraphQL | JSON | Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | +|`/selection/select` |`POST` | GraphQL | JSON - data selected | Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | +|`/selection/preview` |`POST` | GraphQL | JSON - data preview | Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | +|`/selection/profile` |`POST` | GraphQL | JSON - summary | Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | +|`/tasks/service-info` |`GET` | N/A | JSON | Get service information about the service,such as storage details, resource availability, and other documentation| +|`/tasks` |`GET` | N/A | JSON | Get a list of of tasks for the current user| +|`/tasks` |`POST` | Task spec | JSON - with task ID | Create a new task using a task specification (links a selection query and containerised computation task)| +|`/tasks/validate` |`POST` | Task spec | JSON |Validate a task specification| +|`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| +|`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| +|`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | From ac780ea112c18eae51a7a955d2e0b3400cd7e7e4 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Sun, 31 Jan 2021 13:17:19 +0000 Subject: [PATCH 34/51] Updated origins file with more context --- doc/Origins.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/Origins.md b/doc/Origins.md index 4192cda..c689325 100755 --- a/doc/Origins.md +++ b/doc/Origins.md @@ -2,11 +2,19 @@ > Back to the main [README](../README.md) -This API originated in a strawman implementation of a federated data sharing API for an international collaboration on data sharing and federated compute. The collaboration will be launched later in 2020 and more details added. This document summarises the approach that led to the Common API. +## Introduction + +This API originated in a strawman implementation of a federated data sharing API for an international collaboration on data sharing and federated compute. It began with support for a project focused on Alzheimer's and other dementias, [launched in November 2020](https://www.alzheimersdata.org/news/addi-press-release) as the [Alzheimers Disease Data Initiative (ADDI)](https://www.alzheimersdata.org). The objective here was to be able to analyse data in a consistent way, even if some of the of interest could not travel from a data repository. The successful [pilot](https://www.alzheimersdata.org/ad-workbench/pilot-phase) brought together leading groups in the AD research community including [Dementias Platform UK](https://www.dementiasplatform.uk/) and their databank at [UKSerp, Swansea University](https://serp.ac.uk/serp-uk/), [Critical Path Institute](https://c-path.org/) and [GAAIN](http://www.gaain.org/) working with technical partners including [Aridhia Informatics](https://www.aridhia.com). + +Since the initial pilot, the API has also been picked up for use by the [International COVID-19 Data Alliance](https://icoda-research.org/) as it builds out it's initiative and is under also investigation for usage by [Health Data Research UK](https://www.hdruk.ac.uk/) as both look to increase data usage and sharing in a controlled fashion, supporting the [UK ONS five safes model](https://blog.ons.gov.uk/2017/01/27/the-five-safes-data-privacy-at-ons/). We expect the API to evolve rapidly throughout 2021 as it's usage scales across these initiatives and we warmly welcome new collaborators & participants. + +This document summarises the approach that led to the Common API. + +> Please [get in touch](mailto:info@fds-api.org) if you would like to learn more and participate in this effort. ## Approach -Imagine a narrative for a research user who needs to access data. What is their user experience flow, how is that supported by underlying APIs provided by data platforms. +Imagine a narrative for a research user who needs to access data from multiple data repositories, each with their own systems and processes. What is their user experience flow, how is that supported by underlying APIs provided by data platforms? Could that situation be improved through a Common API? For the implementation it should not be too important what their study is about - it might impact researcher accreditation and the type of UX we might want to provide for selection & filtering but not for the generic, Common API. From f752da719fd78d6748dc1d5e87af5315197e2ba0 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 08:56:04 +0000 Subject: [PATCH 35/51] Added HDR UK logo --- README.md | 4 +++- doc/hdruk-logo.png | Bin 0 -> 5614 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100755 doc/hdruk-logo.png diff --git a/README.md b/README.md index 135896e..9e0c91c 100755 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ The Common API is an open source co-development between a number of partner orga [![ADDI logo](./doc/addi-logo.png "ADDI logo")](https://www.alzheimersdata.org/)      -[![ICODA Research logo](./doc/icoda-research-logo.png "Aridhia DRE Logo")](https://www.icoda-research.org) +[![ICODA Research logo](./doc/icoda-research-logo.png "ICODA Research Logo")](https://www.icoda-research.org) +     +[![HDR UK logo](./doc/hdruk-logo.png "HDR UK Logo")](https://www.hdruk.ac.uk)      [![Aridhia DRE logo](./doc/aridhia-dre-logo.png "Aridhia DRE Logo")](https://www.aridhia.com) diff --git a/doc/hdruk-logo.png b/doc/hdruk-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..662d48d5626ee15a8e28ac293e2861664c272819 GIT binary patch literal 5614 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 literal 0 HcmV?d00001 From c422300fd53f2ee712d2385816ece27c68721c61 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:05:09 +0000 Subject: [PATCH 36/51] Restructure front page, add links to source standards --- README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9e0c91c..21575d9 100755 --- a/README.md +++ b/README.md @@ -13,9 +13,12 @@ This repository contains OpenAPI definitions for the Common API for Federated Da A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) -## For Data providers + + + + + +
+**For Data providers** -A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. +A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. The Common API approach allows data providers to choose how they join a collaboration network. @@ -26,10 +29,9 @@ The Common API approach allows data providers to choose how they join a collabor Level 0 is provided by a TRE, while data providers must implement Level 1 or Level 2 using their own infrastructure. Data providers are often in multiple collaborations at the same time. Investment in a Level 1 and Level 2 implementation can be repurposed for more than one network. - -> A reference implementation is being developed to facilitate the technical choices for data providers. - -## For Data users + +**For Data users** A researcher or group of researchers working with multiple data sources have to navigate varying access mechanisms and APIs. By working in a network with data providers that implement the Common API, they can use their favourite tools to query, compute and analyse data in a consistent and efficient way. @@ -40,6 +42,11 @@ The Common API allows users to: - Retrieve record level data (Level 1) or compute over record level data using containerised scripts (Level 2) Currently the API is geared at users within a research team who can program. We expect in time that graphical user interfaces will be built or adapted that take advantage of the standard and reach a wider audience more directly. +
+ +> A reference implementation is being developed to facilitate the technical choices for data providers. ## Partners @@ -53,6 +60,15 @@ The Common API is an open source co-development between a number of partner orga      [![Aridhia DRE logo](./doc/aridhia-dre-logo.png "Aridhia DRE Logo")](https://www.aridhia.com) +## Acknowledgments + +The Common API gratefully builds on work from standardisation communities: + +- [World Wide Web Consortium (W3C)](https://www.w3.org/) +- [GraphQL Foundation](https://foundation.graphql.org/) +- [Global Alliance for Genomics and Health (GA4GH)](https://www.ga4gh.org/) +- [IETF](https://www.ietf.org/) OAuth Working Group - see https://oauth.net/2/ + ## Contributing The code is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/) see [LICENSE](./LICENSE). From f50fa7c6321fa85ce1b515416ea736d205901b8c Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:06:31 +0000 Subject: [PATCH 37/51] Restructure front page, add links to source standards --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21575d9..b681781 100755 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A separate repository provides [Worked examples](https://github.com/federated-da
+ **For Data providers** A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. From 9d89cfdb997f4172326020d41c8fdfbb1d95a026 Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:07:16 +0000 Subject: [PATCH 38/51] Consolidating some links --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b681781..f147282 100755 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ This repository contains OpenAPI definitions for the Common API for Federated Da - [User Guide](./doc/User_Guide.md) - Reference implementation - Coming Soon - [Origins](./doc/Origins.md) - -A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) +- A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) +- *Coming Soon* A reference implementation is being developed to facilitate the technical choices for data providers.
- + **For Data providers** A data provider may be an existing data repository or platform, or groups managing research data at their institutions. They have complex and varying data governance constraints and technical capabiliities which means that contributing data to research projects or more data sharing in a network may be difficult. @@ -47,8 +47,6 @@ Currently the API is geared at users within a research team who can program. We
-> A reference implementation is being developed to facilitate the technical choices for data providers. - ## Partners The Common API is an open source co-development between a number of partner organisations From 8b503ca3e6f2f0130fb27386950ade3f6e7d8ffa Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:08:00 +0000 Subject: [PATCH 39/51] Consolidating some links --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f147282..5500c00 100755 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ This repository contains OpenAPI definitions for the Common API for Federated Da - A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) - *Coming Soon* A reference implementation is being developed to facilitate the technical choices for data providers. +## Summary benefits + diff --git a/doc/User_Guide_Analysis_Plans.md b/doc/User_Guide_Analysis_Plans.md index 7200a2a..509b299 100644 --- a/doc/User_Guide_Analysis_Plans.md +++ b/doc/User_Guide_Analysis_Plans.md @@ -14,7 +14,7 @@ Most existing clinical research and statistics as well as the supporting librari - Package up some of the analysis into Docker containers and queries - Develop scripts to orchestrate the whole thing for reproducibility -Data platforms that implement the Common API commit to helping reduce the friction of remote access in a number of ways intended to help you: +Data platforms that implement the Common API commit to helping reduce the friction of remote access in a number of ways intended to help you as a researcher: - Implementing a *standard* API, which means you don't have to keep learning new ways of getting metadata or processing their data - Providing detailed and up-to-date metadata on data they share through the API, giving you enough detail to adapt your analysis to what data is really there @@ -43,7 +43,7 @@ Examples of staging or phasing analysis might reflect a standard research or dat - modelling data - validating models -A modular and composable approach to your analysis code will allow you to iterate at each stage and if necessary combine the analysis into reproducible process. This should reduce frustrations with not having direct access to data. +A modular and composable approach to your analysis code will allow you to iterate at each stage. This should reduce frustrations with not having direct access to data. It provides some benefit later when trying to package up your work as reproducible research. ## Defining a selection diff --git a/doc/User_Guide_Background.md b/doc/User_Guide_Background.md index ade877e..afc300d 100644 --- a/doc/User_Guide_Background.md +++ b/doc/User_Guide_Background.md @@ -33,7 +33,7 @@ Sites are also free to implement the API as they wish but there are some convent - selection API - implemented, externally accessible - task API - not required -- Level 2: since data cannot be shared the selection API is not only the task API is exposed +- Level 2: since data cannot be shared directly, the selection API is not available externally, but is still used to define the selection to be analysed using the task API: - metadata API - implemented, externally accessible - selection API - implemented, only available within task protocol From 1b2e7e1e24779e310e86fe537ddb7a3a95549bdf Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 25 May 2021 14:20:04 +0100 Subject: [PATCH 45/51] Added banner on the Community call 3/Jun/2021. Will remove later. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 0ad6352..4f27475 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Federated Data Sharing Common API +

+ Our first Common API Community Meeting is planned for 1600 - 1800 UK time Thursday 3rd June 2021. Other time zones: 1500 UTC, 1700 CET, 0800 PDT, 1100 EDT, 2030 India, 0000 Japan, 0100 Sydney/Brisbane. +

+

+ Please contact the maintainers of the repository if you'd like to find out more or attend. +

+ ## Introduction This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The Common API was developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data providers. From eaa10be7c58f4b82dca2b8e2e4ff4aa717cfa7c9 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 25 May 2021 14:22:23 +0100 Subject: [PATCH 46/51] Replace HTML with simple markdown. --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4f27475..d747eaf 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Federated Data Sharing Common API -

- Our first Common API Community Meeting is planned for 1600 - 1800 UK time Thursday 3rd June 2021. Other time zones: 1500 UTC, 1700 CET, 0800 PDT, 1100 EDT, 2030 India, 0000 Japan, 0100 Sydney/Brisbane. -

-

- Please contact the maintainers of the repository if you'd like to find out more or attend. -

+## Upcoming community meeting + +Our first Common API Community Meeting is planned for **1600 - 1800 UK time Thursday 3rd June 2021**. Other time zones: 1500 UTC, 1700 CET, 0800 PDT, 1100 EDT, 2030 India, 0000 Japan, 0100 Sydney/Brisbane. + +Please contact the [maintainers of the repository](mailto:info@fds-api.org) if you'd like to find out more or attend. ## Introduction From 593985786c98929702392f1ad734963989a2fefa Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Fri, 11 Jun 2021 15:37:08 +0100 Subject: [PATCH 47/51] Removing announcement for the community meeting --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index d747eaf..0ad6352 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ # Federated Data Sharing Common API -## Upcoming community meeting - -Our first Common API Community Meeting is planned for **1600 - 1800 UK time Thursday 3rd June 2021**. Other time zones: 1500 UTC, 1700 CET, 0800 PDT, 1100 EDT, 2030 India, 0000 Japan, 0100 Sydney/Brisbane. - -Please contact the [maintainers of the repository](mailto:info@fds-api.org) if you'd like to find out more or attend. - ## Introduction This repository contains OpenAPI definitions for the Common API for Federated Data Sharing. The Common API was developed to facilitate collaboration and trusted data sharing networks between trusted research environments and data providers. From 204e9c2c0b3b6eab643c768351728bb13d9e9650 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Sun, 17 Oct 2021 15:33:39 +0100 Subject: [PATCH 48/51] Removing -alpha tag from VERSION as currently stable --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1c6f7de..9084fa2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0-alpha +1.1.0 From 349e8d2cd29c04d5229740b8b27531c0e58a2258 Mon Sep 17 00:00:00 2001 From: Lawrence Setien <84802317+LawrenceEVS@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:43:49 -0300 Subject: [PATCH 49/51] Update API_Overview.md --- doc/API_Overview.md | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/doc/API_Overview.md b/doc/API_Overview.md index e345e47..0a80e83 100644 --- a/doc/API_Overview.md +++ b/doc/API_Overview.md @@ -50,24 +50,26 @@ For maximum flexibility each section of the Common API is defined in separate gi Details of each endpoint: -|Endpoint |HTTP | Payload | Result | Summary | -|:----------------------------------------------------|:------|:------------|:----------|:----------------------------------------------------------| -|`/datasets` |`GET` | N/A | JSON | Get a list of available datasets. Shows the list of all datasets available for querying. | -|`/datasets/{datasetid}` |`GET` | N/A | JSON | Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/catalogue` |`GET` | N/A | DCAT JSON | Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries` |`GET` | N/A | Dictionary JSON | Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | -|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` | N/A | Dictionary JSON | Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | -|`/selection/validate` |`POST` | GraphQL | JSON | Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | -|`/selection/beacon` |`POST` | GraphQL | JSON | Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | -|`/selection/select` |`POST` | GraphQL | JSON - data selected | Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | -|`/selection/preview` |`POST` | GraphQL | JSON - data preview | Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | -|`/selection/profile` |`POST` | GraphQL | JSON - summary | Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | -|`/tasks/service-info` |`GET` | N/A | JSON | Get service information about the service,such as storage details, resource availability, and other documentation| -|`/tasks` |`GET` | N/A | JSON | Get a list of of tasks for the current user| -|`/tasks` |`POST` | Task spec | JSON - with task ID | Create a new task using a task specification (links a selection query and containerised computation task)| -|`/tasks/validate` |`POST` | Task spec | JSON |Validate a task specification| -|`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| -|`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| -|`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | +|Available |Endpoint |HTTP | Payload | Result | Summary | +|:---- |:---------------------------------------------|:------|:------------|:----------|:----------------------------------------------------------| +|:heavy_check_mark: |`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | +|:heavy_check_mark: |`/service-info` |`GET` | N/A | JSON | Get the FDSA general info set on the administrator API. | +|:heavy_check_mark: |`/datasets` |`GET` | N/A | JSON | Get a list of available datasets. Shows the list of all datasets available for querying. | +|:heavy_check_mark: |`/datasets/{datasetid}` |`GET` | N/A | JSON | Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | +|:heavy_check_mark: |`/datasets/{datasetid}/catalogue` |`GET` | N/A | DCAT JSON | Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | +|:heavy_check_mark: |`/datasets/{datasetid}/dictionaries` |`GET` | N/A | Dictionary JSON | Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | +|:heavy_check_mark: |`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` | N/A | Dictionary JSON | Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | +|:heavy_check_mark: |`/selection/validate` |`POST` | GraphQL | JSON | Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | +|:heavy_check_mark: |`/selection/beacon` |`POST` | GraphQL | JSON | Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | +|:heavy_check_mark: |`/selection/select` |`POST` | GraphQL | JSON - data selected | Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | +|:x: |`/selection/preview` |`POST` | GraphQL | JSON - data preview | Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | +|:x: |`/selection/profile` |`POST` | GraphQL | JSON - summary | Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | +|:heavy_check_mark: |`/tasks/service-info` |`GET` | N/A | JSON | Get service information about the service,such as storage details, resource availability, and other documentation| +|:heavy_check_mark: |`/tasks` |`GET` | N/A | JSON | Get a list of of tasks for the current user| +|:heavy_check_mark: |`/tasks` |`POST` | Task spec | JSON - with task ID | Create a new task using a task specification (links a selection query and containerised computation task)| +|:heavy_check_mark: |`/tasks/validate` |`POST` | Task spec | JSON |Validate a task specification| +|:heavy_check_mark: |`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| +|:heavy_check_mark: |`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| +|:heavy_check_mark: |`/tasks/{task_uuid}/results` |`GET` | N/A | ZIP | Get a specific COMPLETED task results. | > Note that the following endpoints are experimental at version 1.1: `/selection/beacon`, `/selection/preview` and `/selection/profile` - they are expected to be firmed up in later versions. From 1eebfb132b49cb8e489a66cadafc333580b956a5 Mon Sep 17 00:00:00 2001 From: kelly-sparks-alzheimersdata-org <81629328+kelly-sparks-alzheimersdata-org@users.noreply.github.com> Date: Tue, 20 Sep 2022 12:41:01 -0400 Subject: [PATCH 50/51] Revert "Update API_Overview.md" --- doc/API_Overview.md | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/doc/API_Overview.md b/doc/API_Overview.md index 0a80e83..e345e47 100644 --- a/doc/API_Overview.md +++ b/doc/API_Overview.md @@ -50,26 +50,24 @@ For maximum flexibility each section of the Common API is defined in separate gi Details of each endpoint: -|Available |Endpoint |HTTP | Payload | Result | Summary | -|:---- |:---------------------------------------------|:------|:------------|:----------|:----------------------------------------------------------| -|:heavy_check_mark: |`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | -|:heavy_check_mark: |`/service-info` |`GET` | N/A | JSON | Get the FDSA general info set on the administrator API. | -|:heavy_check_mark: |`/datasets` |`GET` | N/A | JSON | Get a list of available datasets. Shows the list of all datasets available for querying. | -|:heavy_check_mark: |`/datasets/{datasetid}` |`GET` | N/A | JSON | Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | -|:heavy_check_mark: |`/datasets/{datasetid}/catalogue` |`GET` | N/A | DCAT JSON | Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | -|:heavy_check_mark: |`/datasets/{datasetid}/dictionaries` |`GET` | N/A | Dictionary JSON | Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | -|:heavy_check_mark: |`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` | N/A | Dictionary JSON | Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | -|:heavy_check_mark: |`/selection/validate` |`POST` | GraphQL | JSON | Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | -|:heavy_check_mark: |`/selection/beacon` |`POST` | GraphQL | JSON | Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | -|:heavy_check_mark: |`/selection/select` |`POST` | GraphQL | JSON - data selected | Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | -|:x: |`/selection/preview` |`POST` | GraphQL | JSON - data preview | Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | -|:x: |`/selection/profile` |`POST` | GraphQL | JSON - summary | Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | -|:heavy_check_mark: |`/tasks/service-info` |`GET` | N/A | JSON | Get service information about the service,such as storage details, resource availability, and other documentation| -|:heavy_check_mark: |`/tasks` |`GET` | N/A | JSON | Get a list of of tasks for the current user| -|:heavy_check_mark: |`/tasks` |`POST` | Task spec | JSON - with task ID | Create a new task using a task specification (links a selection query and containerised computation task)| -|:heavy_check_mark: |`/tasks/validate` |`POST` | Task spec | JSON |Validate a task specification| -|:heavy_check_mark: |`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| -|:heavy_check_mark: |`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| -|:heavy_check_mark: |`/tasks/{task_uuid}/results` |`GET` | N/A | ZIP | Get a specific COMPLETED task results. | +|Endpoint |HTTP | Payload | Result | Summary | +|:----------------------------------------------------|:------|:------------|:----------|:----------------------------------------------------------| +|`/datasets` |`GET` | N/A | JSON | Get a list of available datasets. Shows the list of all datasets available for querying. | +|`/datasets/{datasetid}` |`GET` | N/A | JSON | Get Catalogue entry (metadata) and Dictionaries (field descriptions) for dataset. Returns the catalogue metadata and a list of field descriptions for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/catalogue` |`GET` | N/A | DCAT JSON | Get Catalogue entry (metadata) for dataset. Returns the catalogue metadata for a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries` |`GET` | N/A | Dictionary JSON | Get Dictionaries (field descriptions) for dataset. Returns a list of field descriptions for each table within a specified dataset (by dataset ID). | +|`/datasets/{datasetid}/dictionaries/{tableid}` |`GET` | N/A | Dictionary JSON | Get a single dataset Dictionary for a specified table. Returns a set field descriptions for the specified table (by table ID) within a specified dataset (by dataset ID). | +|`/selection/validate` |`POST` | GraphQL | JSON | Validate a given selection query. With a simple GraphQL query, check whether the query is valid and corresponds to real fields at this location. | +|`/selection/beacon` |`POST` | GraphQL | JSON | Get a Beacon (T/F) for a specified data selection. With a simple Graph QL query, check which locations contain data relevant to a specific query. | +|`/selection/select` |`POST` | GraphQL | JSON - data selected | Perform a selection operation on a dataset. With a simple Graph QL query, returns the full selection of data in a JSON or .csv format. | +|`/selection/preview` |`POST` | GraphQL | JSON - data preview | Preview the results of a selection operation on a dataset. With a simple Graph QL query, returns a small sample of the selection in a JSON or .csv format. | +|`/selection/profile` |`POST` | GraphQL | JSON - summary | Get a profile of a selection operation on a dataset. Returns a set of metrics for the given selection operation. | +|`/tasks/service-info` |`GET` | N/A | JSON | Get service information about the service,such as storage details, resource availability, and other documentation| +|`/tasks` |`GET` | N/A | JSON | Get a list of of tasks for the current user| +|`/tasks` |`POST` | Task spec | JSON - with task ID | Create a new task using a task specification (links a selection query and containerised computation task)| +|`/tasks/validate` |`POST` | Task spec | JSON |Validate a task specification| +|`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| +|`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| +|`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | > Note that the following endpoints are experimental at version 1.1: `/selection/beacon`, `/selection/preview` and `/selection/profile` - they are expected to be firmed up in later versions. From 04932b9e917353fb4b3724e60e73b5caab140ee5 Mon Sep 17 00:00:00 2001 From: lawrence Date: Tue, 20 Sep 2022 14:27:15 -0300 Subject: [PATCH 51/51] Revert "Removing -alpha tag from VERSION as currently stable" This reverts commit 204e9c2c0b3b6eab643c768351728bb13d9e9650. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9084fa2..1c6f7de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.1.0-alpha
From c76c850ff6d65743c07c98d0cd9e4b68f2b0393e Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:09:12 +0000 Subject: [PATCH 40/51] Consolidating some links --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5500c00..52d8f62 100755 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ This repository contains OpenAPI definitions for the Common API for Federated Da - [API Overview](./doc/API_Overview.md) - [User Guide](./doc/User_Guide.md) -- Reference implementation - Coming Soon - [Origins](./doc/Origins.md) - A separate repository provides [Worked examples](https://github.com/federated-data-sharing/common-api-examples) - *Coming Soon* A reference implementation is being developed to facilitate the technical choices for data providers. From a1386daef9b6c2d8eb06aacd9c7f308efb26987a Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:10:22 +0000 Subject: [PATCH 41/51] Better spacing of logos with 2nd row --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52d8f62..6425d1b 100755 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ The Common API is an open source co-development between a number of partner orga [![ICODA Research logo](./doc/icoda-research-logo.png "ICODA Research Logo")](https://www.icoda-research.org)      [![HDR UK logo](./doc/hdruk-logo.png "HDR UK Logo")](https://www.hdruk.ac.uk) -     + [![Aridhia DRE logo](./doc/aridhia-dre-logo.png "Aridhia DRE Logo")](https://www.aridhia.com) ## Acknowledgments From 4007807e0a111e4349e7fbc051e2efc9c8cd0b9f Mon Sep 17 00:00:00 2001 From: "rodrigo.barnes@aridhia.com" Date: Mon, 1 Feb 2021 09:24:07 +0000 Subject: [PATCH 42/51] Clarification notes on OAuth2 and some selection endpoints --- doc/API_Overview.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/API_Overview.md b/doc/API_Overview.md index 444c31f..e345e47 100644 --- a/doc/API_Overview.md +++ b/doc/API_Overview.md @@ -25,7 +25,7 @@ By adopting the API, a data provider and their network can implement “connecto Rather than reinventing the wheel, the Common API **adopts and adapts** existing standards efforts - The API is defined Open API specifications. -- API endpoints should be authenticated using OAuth tokens (out of band for this version) +- API endpoints should be authenticated using OAuth2 (will be mandated in future versions) - Descriptive metadata is defined in a variant of the [W3C DCAT](https://www.w3.org/TR/vocab-dcat-2/) standard and a simple data dictionary model. - Selections are defined in [GraphQL](https://graphql.org/) as an abstraction over querying, selection and filtering - Federated computations are defined in a variant of the [GA4GH Task Execution Service (TES) API](http://ga4gh.github.io/task-execution-schemas/) @@ -69,3 +69,5 @@ Details of each endpoint: |`/tasks/{task_id}` |`GET` | N/A | JSON - task details | Get task details including status. If available, includes a link to the output of the task| |`/tasks/{task_id}/cancel` |`POST` | N/A | JSON - task status |Cancel a task| |`/health_check` |`GET` | N/A | JSON | Get a health check of the service. | + +> Note that the following endpoints are experimental at version 1.1: `/selection/beacon`, `/selection/preview` and `/selection/profile` - they are expected to be firmed up in later versions. From 35b793d2bfac0f1a63ebbb970417f4a196a52051 Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Tue, 13 Apr 2021 08:52:40 +0000 Subject: [PATCH 43/51] Correcting typos and clarifying some explanations. --- doc/User_Guide_Analysis_Plans.md | 4 +--- doc/User_Guide_Background.md | 6 +++--- doc/User_Guide_CLI.md | 6 +++--- doc/User_Guide_Containerising_Tasks.md | 9 +++++++-- doc/User_Guide_Key_Payloads.md | 4 ++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/doc/User_Guide_Analysis_Plans.md b/doc/User_Guide_Analysis_Plans.md index e8aaa9b..7200a2a 100644 --- a/doc/User_Guide_Analysis_Plans.md +++ b/doc/User_Guide_Analysis_Plans.md @@ -34,7 +34,7 @@ In return, you should revisit your analysis plan and structure it to the remote Examples of staging or phasing analysis might reflect a standard research or data science life cycle: -- exploratory analysis: systematically validating, summarising or exploring the remote data early on in orde +- exploratory analysis: systematically validating, summarising or exploring the remote data early on in order to define the main statistical analysis - quality checks, outlier analysis - sampling - visualisation @@ -65,8 +65,6 @@ For example, we can imagine a table called `virtual_cohort` with fields that inc ``` More details of how define GraphQL queries can be found on the [community pages](https://graphql.org/learn/) - bear in mind for this use case, we only need to know about selection *queries* and can ignore schemas and mutations. - - ## Next steps Understand [Containerising scripts](./User_Guide_Containerising_Tasks.md) \ No newline at end of file diff --git a/doc/User_Guide_Background.md b/doc/User_Guide_Background.md index 25cc8bb..ade877e 100644 --- a/doc/User_Guide_Background.md +++ b/doc/User_Guide_Background.md @@ -10,7 +10,7 @@ Federated Analysis and data sharing is a strategy to network data platforms and - site - a data repository implementing the API - client - a user's programme interacting with the API -- container - a Docker container encapsulating +- container - a Docker container encapsulating the analysis or computation require ## High-level workflow @@ -45,9 +45,9 @@ For more details, see the [API Overview](./API_Overview.md) ## Accessing the API -The API is a RESTful standard web-based programming interface, and user can select whatever client language or tool that they want. We have tested using `curl`, `python` and `R` as well as graphical clients like Postman. We assume the user is familiar with programmatic access to [Web API](https://en.wikipedia.org/wiki/Web_API) endpoints. +The API is a RESTful standard web-based programming interface, and the user can select whatever client language or tool that they want. We have tested using `curl`, `python` and `R` as well as graphical clients like Postman. We assume the user is familiar with programmatic access to [Web API](https://en.wikipedia.org/wiki/Web_API) endpoints. -We assume the user has been provided credentials to obtain a bearer token for API requests. The method for providing a token is not currently part of the specification but in a typical [OAuth](https://en.wikipedia.org/wiki/OAuth) model, a user is provided client ID and client secret (password). Using those credentials, they call an API endpoint and obtain a token. This token is added as a header on subsequent calls. The token is intended to provide authentication AND authorisation. Sites are free to change the output of API calls based on what the individual user is authorised. +We assume the user has been provided credentials to obtain a bearer token for API requests. The method for providing a token is not currently part of the specification but in a typical [OAuth](https://en.wikipedia.org/wiki/OAuth) model, a user is provided a client ID and client secret (password). Using those credentials, they call an API endpoint and obtain a token. This token is added as a header on subsequent calls. The token is intended to provide authentication AND authorisation. Sites are free to change the output of API calls based on what the individual user is authorised. Examples below are provided in `R`, `python` and `curl` in a Linux environment or similar. diff --git a/doc/User_Guide_CLI.md b/doc/User_Guide_CLI.md index 9b39b52..36da7c5 100644 --- a/doc/User_Guide_CLI.md +++ b/doc/User_Guide_CLI.md @@ -6,7 +6,7 @@ Using `curl` and `jq` on the command line is a low level way to interact with the API in shell scripts. -We assuming the API is accessible at an endpoint `FDS_ENDPOINT`. For example, if you run the reference implementation, this will be: +We assume the API is accessible at an endpoint `FDS_ENDPOINT`. For example, if you run the reference implementation, this will be: ```sh FDS_ENDPOINT="https://localhost:8443/federated-data-sharing/1.1.0" @@ -49,7 +49,7 @@ curl $curl_opts -X GET -H "Accept: application/json"\ "$FDS_ENDPOINT/datasets/$dataset_id/catalogue" | jq ``` -... and then get the dictionary for that dataset - not that there may multiple 'tables' within the dataset, with individual dictionaries. +... and then get the dictionary for that dataset - note that there may multiple 'tables' within the dataset, with individual dictionaries. ```sh curl $curl_opts -X GET -H "Accept: application/json"\ "$FDS_ENDPOINT/datasets/$dataset_id/dictionaries"\ @@ -70,7 +70,7 @@ curl $curl_opts -X POST\ "$FDS_ENDPOINT/selection/validate" | jq ``` -To then select using that same query: +And then select using that same query: ```sh curl $curl_opts -X POST\ -H "Authorization: Bearer $token" -H "Accept: application/json"\ diff --git a/doc/User_Guide_Containerising_Tasks.md b/doc/User_Guide_Containerising_Tasks.md index 83bfe40..742770c 100644 --- a/doc/User_Guide_Containerising_Tasks.md +++ b/doc/User_Guide_Containerising_Tasks.md @@ -6,7 +6,7 @@ In order to execute a remote computation task, your analysis code must be packaged up in a docker container. -> For simplicity we use the word "script" for this code as this is common data science but your could be anything that is containerised including complex programmes with library or package dependencies all encapsulated within the container. +> For simplicity we use the word "script" for this code as this is common data science but your container could be any program that can be run in a docker container, including complex programmes with library or package dependencies all encapsulated within the container. ## Workflow @@ -22,7 +22,12 @@ A set of conventions set out how you should expect inputs to your containers or ![Reading and writing from the container](./sketch_docker.jpg) -When running a federated task in a more complex scenario, the same container might be used across multiple sites, or a selection filter might process only some of the data available at a given site. When combining data, it is useful to consider what 'post-processing' produces the final output you require: +Federated analysis can be run on more than one node or site. When running a federated task in that more complex scenario, the same container might be used across multiple sites, or a selection filter might process only some of the data available at a given site. In this case it is important to consider a two stage process: + +- distribute the code to each site and obtain intermediate results +- combine data in a post-processing step + +The 'post-processing' step is what produced the final output you are aiming for: ![Overview](./sketch_full.jpg) diff --git a/doc/User_Guide_Key_Payloads.md b/doc/User_Guide_Key_Payloads.md index a0fcfe7..acc6745 100644 --- a/doc/User_Guide_Key_Payloads.md +++ b/doc/User_Guide_Key_Payloads.md @@ -4,7 +4,7 @@ The metadata API is a read-only API to discover and navigate what data might be available at a site. The API uses specific payloads to define tasks or selections. These can be constructed programmatically or in files passed to commands and libraries. -Selections are currently defined in [GraphQL](https://graphql.org/) and posted with `Content-type: plain-text`. This was chosen to abstact from specific query languages like SQL or RDF and to leverage a wider range of underlying data management technologies. A selection query is defined using GraphQL [queries](https://graphql.org/learn/queries/). Broadly speaking the structure for a query selection of fields `field1`, `field2` and `field3` fom the table `table_name` the GraphQL would look like: +Selections are currently defined in [GraphQL](https://graphql.org/) and posted with `Content-type: plain-text`. This was chosen to abstact from specific query languages like SQL or RDF and to leverage a wider range of underlying data management technologies. A selection query is defined using GraphQL [queries](https://graphql.org/learn/queries/). Broadly speaking for a query selection of fields `field1`, `field2` and `field3` fom the table `table_name` the GraphQL would look like: ``` { @@ -67,7 +67,7 @@ The structure of the JSON is: | executors/image | The URL of a container image (in an approved registry) | | resources | Not enforced now but estimates the compute resources for the task | -A task can assume that inputs are provided in the `/mnt/input` folder attached to their container, wheres outputs can be written to `/mnt/output`. Logs may be delivered to `/mnt/logs`. +A task can assume that inputs are provided in the `/mnt/input` folder attached to their container, whereas outputs can be written to `/mnt/output`. Logs may be delivered to `/mnt/logs`. ## Next Step From e404aff005a1beed12f8916f757a4f98b11c226c Mon Sep 17 00:00:00 2001 From: Rodrigo Barnes Date: Fri, 21 May 2021 07:13:10 +0100 Subject: [PATCH 44/51] Clarification of wording in documentation. --- README.md | 4 ++-- doc/User_Guide_Analysis_Plans.md | 4 ++-- doc/User_Guide_Background.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6425d1b..0ad6352 100755 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ The Common API approach allows data providers to choose how they join a collabor - Level 0: transferring data directly for hosting to a trusted research environment (TRE) - Level 1: providing remote access to data -- Level 2: providing a data providers and data users in biomedical reserach +- Level 2: providing remote computation on data held at source -Level 0 is provided by a TRE, while data providers must implement Level 1 or Level 2 using their own infrastructure. +Level 0 is provided by a Trusted Research Environment (TRE), while data providers must implement Level 1 or Level 2 using their own infrastructure. Data providers are often in multiple collaborations at the same time. Investment in a Level 1 and Level 2 implementation can be repurposed for more than one network.