From 8d9604edf298fa55bc11503824fccd050e958f59 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Sun, 5 Nov 2017 16:12:09 +0100
Subject: [PATCH] domain annotation server (prototype)

---
 package-lock.json                            | Bin 156627 -> 170121 bytes
 package.json                                 |  14 ++-
 src/apps/domain-annotation-server/mapping.ts | 102 +++++++++++++++++++
 src/apps/domain-annotation-server/schemas.ts |  89 ++++++++++++++++
 src/apps/domain-annotation-server/server.ts  |  48 +++++++++
 src/apps/domain-annotation-server/test.ts    |  14 +++
 src/apps/domain-annotation-server/utils.ts   |  43 ++++++++
 src/mol-io/writer/cif/encoder.ts             |   2 +-
 8 files changed, 308 insertions(+), 4 deletions(-)
 create mode 100644 src/apps/domain-annotation-server/mapping.ts
 create mode 100644 src/apps/domain-annotation-server/schemas.ts
 create mode 100644 src/apps/domain-annotation-server/server.ts
 create mode 100644 src/apps/domain-annotation-server/test.ts
 create mode 100644 src/apps/domain-annotation-server/utils.ts

diff --git a/package-lock.json b/package-lock.json
index 1ce79bb2dc8d4d8557caa3e2f73f46de4c4992d2..445a602d57c899a8e98f013ba74f05382363076a 100644
GIT binary patch
delta 7135
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
SaBxPJkzWAFRmxUO8#Dp#u@M;n

delta 749
zcmeC&$aVQRr%*MQf`U?BVs5IEm4Z@keokioMp0+R&9ZE~jGIk4*cd0j<36}~KQFJy
z=E+K02Aki8>IiN=k^W3<@{=tJoA=gVXWT5-?8Y^n?;Ru8WP?cxn~QsuL^gk0bcB7g
z@>&bF%`#i$**8DmEziEW@c4HA%~x-1X5IYh{x$XOl3a{`87DKdD{NQhVJu?-vz7@k
zPGj2sU6@f0Z2Wd5EyjyU+qd*G-eK7;F@>?6X?o{$#%0r09GMiR+st6xGucO4d%M6)
z#%k8dQ(g;CUckdUea1Y-hugasGS>5LXW7WOOJMqqMn<0Le9sv<CckCkneM>BWHvqF
zIiu9}XNMS{GfmIwW8#}^`<QQfK^>#X^oAphtlQrlV@%-Ro^+j2i+MWhJ4VIrvu-iE
zvu;;-%$TG#IW$Ii`U7PqzU>ounV8tO&y`@>&$vBBnyHL`y4Wj5{>f>KV$<^-nM}67
z)@HKOVKkZUxRg<Ix_<<d*7P7oCf(^~tV}A?56olanZBlzNoTtFFGdkYlj(~08C51f
z$Y%*l3^y+h^A0FYtMn-J&#kQVsY=)P(zY-&Fe@|7OD%IsPt3{m%n38k3oQuqEq2Mv
zcXAKYPfiN<@ym*GOt&mFa866n*G^3=_73w7v?%j0wMfe|44l5vk<oGb`iV@e)3e!_
zRHr)xFmX>8jbaj-++!fbXgGZ#Ba_AStSBb&>4(agRHZHba*|D|vXe6-JhGE=LR_oL
z&6AunJyOCe1E(j}F*!{C7{w$udG}ZTX(0M|F5mR_4~!z)EuxuTFikg(XZpx!HrY_p
za5`Ty6Z`bo1SVldv&j#+ltT53ic>2qT)kbxe4X+wjLa=_3JdbR(p}7)^)vOIJu|f}
zL%dwd!gG_fBl6913w^!Ivm7lv^Nl@yJ#vyG0;{qtEz?qi%&Lm~EOL#@3k%C~_46X9
sGrnUKpI*?)s4zW1h>3f<RwB~{mhEfOn37qxU&v<4X4?Kdk7<!60HA>g`~Uy|

diff --git a/package.json b/package.json
index d6ddc61c4..3185d9554 100644
--- a/package.json
+++ b/package.json
@@ -24,15 +24,20 @@
     "transform": {
       "\\.ts$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
     },
-    "moduleDirectories": ["node_modules", "build/node_modules"], 
+    "moduleDirectories": [
+      "node_modules",
+      "build/node_modules"
+    ],
     "testRegex": "\\.spec\\.ts$"
   },
   "author": "",
   "license": "MIT",
   "devDependencies": {
     "@types/benchmark": "^1.0.30",
+    "@types/express": "^4.0.39",
     "@types/jest": "^21.1.5",
     "@types/node": "^8.0.47",
+    "@types/node-fetch": "^1.6.7",
     "benchmark": "^2.1.4",
     "download-cli": "^1.0.5",
     "jest": "^21.2.1",
@@ -45,8 +50,11 @@
     "ts-jest": "^21.1.4",
     "tslint": "^5.8.0",
     "typescript": "^2.6.1",
-    "uglify-js": "^3.1.6",
+    "uglify-js": "^3.1.7",
     "util.promisify": "^1.0.0"
   },
-  "dependencies": {}
+  "dependencies": {
+    "express": "^4.16.2",
+    "node-fetch": "^1.7.3"
+  }
 }
diff --git a/src/apps/domain-annotation-server/mapping.ts b/src/apps/domain-annotation-server/mapping.ts
new file mode 100644
index 000000000..4489cb959
--- /dev/null
+++ b/src/apps/domain-annotation-server/mapping.ts
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Table } from 'mol-base/collections/database'
+import { CIFEncoder, create as createEncoder } from 'mol-io/writer/cif'
+import * as S from './schemas'
+import { getCategoryInstanceProvider } from './utils'
+
+export default function create(allData: any) {
+    const mols = Object.keys(allData);
+    if (!mols.length) return '#';
+
+    const data = allData[mols[0]];
+
+    const enc = createEncoder();
+    enc.startDataBlock(mols[0]);
+
+    for (const cat of Object.keys(S.categories)) {
+        writeDomain(enc, getDomain(cat, (S.categories as any)[cat], data));
+    }
+    return enc.getData();
+}
+
+interface DomainAnnotation {
+    name: string,
+    domains: Table<any>,
+    mappings: Table<S.mapping>
+}
+type MappingRow = Table.Row<S.mapping>;
+
+function writeDomain(enc: CIFEncoder<any>, domain: DomainAnnotation | undefined) {
+    if (!domain) return;
+    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_annotation`, domain.domains));
+    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_mapping`, domain.mappings));
+}
+
+function getMappings(startId: number, group_id: number, mappings: any): MappingRow[] {
+    const rows: MappingRow[] = [];
+
+    const n = (v: any) => v === null ? void 0 : v;
+
+    for (const entry of mappings) {
+        if (entry.start && entry.end) {
+            rows.push({
+                id: startId++,
+                group_id,
+                label_entity_id: '' + entry.entity_id,
+                label_asym_id: entry.struct_asym_id,
+                auth_asym_id: entry.chain_id,
+                beg_label_seq_id: n(entry.start.residue_number),
+                beg_auth_seq_id: n(entry.start.author_residue_number),
+                pdbx_beg_PDB_ins_code: entry.start.author_insertion_code,
+                end_label_seq_id: n(entry.end.residue_number),
+                end_auth_seq_id: n(entry.end.author_residue_number),
+                pdbx_end_PDB_ins_code: entry.end.author_insertion_code
+            });
+        } else {
+            rows.push({
+                id: startId++,
+                group_id,
+                label_entity_id: '' + entry.entity_id,
+                label_asym_id: entry.struct_asym_id,
+                auth_asym_id: entry.chain_id
+            } as any);
+        }
+    }
+    return rows;
+}
+
+function getDomainInfo(id: string, mapping_group_id: number, data: any, schema: any) {
+    const props = Object.create(null);
+    for (const k of Object.keys(schema)) props[k] = data[k];
+    return { id, mapping_group_id, identifier: data.identifier, ...props };
+}
+
+function getDomain(name: string, schema: any, allData: any) {
+    if (!allData[name]) return void 0;
+
+    const data = allData[name];
+
+    const domains: any[] = [];
+    const mappings: MappingRow[] = [];
+
+    let mappingSerialId = 1, mapping_group_id = 1;
+
+    for (const id of Object.keys(data)) {
+        const domain = data[id];
+        domains.push(getDomainInfo(id, mapping_group_id, domain, schema));
+        mappings.push(...getMappings(mappingSerialId, mapping_group_id, domain.mappings));
+        mappingSerialId = mappings.length + 1;
+        mapping_group_id++;
+    }
+
+    return domains.length > 0 ? {
+        name,
+        domains: Table.ofRows({ ...S.Base, ...schema }, domains),
+        mappings: Table.ofRows(S.mapping, mappings)
+    } : void 0;
+}
\ No newline at end of file
diff --git a/src/apps/domain-annotation-server/schemas.ts b/src/apps/domain-annotation-server/schemas.ts
new file mode 100644
index 000000000..370bf1786
--- /dev/null
+++ b/src/apps/domain-annotation-server/schemas.ts
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Column } from 'mol-base/collections/database'
+
+import Type = Column.Type
+
+export const Base = {
+    id: Type.str,
+    identifier: Type.str,
+    mapping_group_id: Type.int
+}
+export type Base = typeof Base
+
+export const mapping = {
+    id: Type.int,
+    group_id: Type.int,
+
+    label_entity_id: Type.str,
+    label_asym_id: Type.str,
+    auth_asym_id: Type.str,
+
+    beg_label_seq_id: Type.int,
+    beg_auth_seq_id: Type.int,
+    pdbx_beg_PDB_ins_code: Type.str,
+
+    end_label_seq_id: Type.int,
+    end_auth_seq_id: Type.int,
+    pdbx_end_PDB_ins_code: Type.str
+}
+export type mapping = typeof mapping
+
+export const Pfam = {
+    description: Type.str
+}
+export type Pfam = typeof Pfam
+
+export const InterPro = {
+    name: Type.str
+}
+export type InterPro = typeof InterPro
+
+export const CATH = {
+    name: Type.str,
+    homology: Type.str,
+    architecture: Type.str,
+    identifier: Type.str,
+    class: Type.str,
+    topology: Type.str,
+}
+export type CATH = typeof CATH
+
+export const EC = {
+    accepted_name: Type.str,
+    reaction: Type.str,
+    systematic_name: Type.str
+}
+export type EC = typeof EC
+
+export const UniProt = {
+    name: Type.str
+}
+export type UniProt = typeof UniProt
+
+export const SCOP = {
+    sccs: Type.str,
+    description: Type.str
+}
+export type SCOP = typeof SCOP
+
+export const GO = {
+    category: Type.str,
+    definition: Type.str,
+    name: Type.str
+}
+export type GO = typeof GO
+
+export const categories = {
+    Pfam,
+    InterPro,
+    CATH,
+    EC,
+    UniProt,
+    SCOP,
+    GO
+}
\ No newline at end of file
diff --git a/src/apps/domain-annotation-server/server.ts b/src/apps/domain-annotation-server/server.ts
new file mode 100644
index 000000000..1d4f192ca
--- /dev/null
+++ b/src/apps/domain-annotation-server/server.ts
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as express from 'express'
+import fetch from 'node-fetch'
+import createMapping from './mapping'
+
+async function getMappings(id: string) {
+    const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);
+    const json = await data.json();
+    return createMapping(json);
+};
+
+
+let PORT = process.env.port || 1338;
+
+const app = express();
+
+const PREFIX = '/'
+
+app.get(`${PREFIX}/:id`, async (req, res) => {
+    try {
+        console.log('Requesting ' + req.params.id);
+        const mapping = await getMappings(req.params.id);
+        res.writeHead(200, {
+            'Content-Type': 'text/plain; charset=utf-8',
+            'Access-Control-Allow-Origin': '*',
+            'Access-Control-Allow-Headers': 'X-Requested-With'
+        });
+        res.end(mapping);
+    } catch {
+        console.log('Failed ' + req.params.id);
+        res.writeHead(404, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'X-Requested-With' });
+        res.end();
+    }
+});
+
+app.get(`${PREFIX}`, (req, res) => {
+    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
+    res.end('Usage: /pdb_id, e.g. /1tqn');
+})
+
+app.listen(PORT);
+
+console.log('Running on port ' + PORT);
\ No newline at end of file
diff --git a/src/apps/domain-annotation-server/test.ts b/src/apps/domain-annotation-server/test.ts
new file mode 100644
index 000000000..66911dec6
--- /dev/null
+++ b/src/apps/domain-annotation-server/test.ts
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import fetch from 'node-fetch'
+import createMapping from './mapping'
+
+(async function () {
+    const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');
+    const json = await data.json();
+    console.log(createMapping(json));
+}());
\ No newline at end of file
diff --git a/src/apps/domain-annotation-server/utils.ts b/src/apps/domain-annotation-server/utils.ts
new file mode 100644
index 000000000..cb5c6badd
--- /dev/null
+++ b/src/apps/domain-annotation-server/utils.ts
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Table } from 'mol-base/collections/database'
+import Iterator from 'mol-base/collections/iterator'
+import * as Encoder from 'mol-io/writer/cif'
+
+function columnValue(k: string) {
+    return (i: number, d: any) => d[k].value(i);
+}
+
+function columnValueKind(k: string) {
+    return (i: number, d: any) => d[k].valueKind(i);
+}
+
+function ofSchema(schema: Table.Schema) {
+    const fields: Encoder.FieldDefinition[] = [];
+    for (const k of Object.keys(schema)) {
+        const t = schema[k];
+        // TODO: matrix/vector/support
+        const type = t.kind === 'str' ? Encoder.FieldType.Str : t.kind === 'int' ? Encoder.FieldType.Int : Encoder.FieldType.Float;
+        fields.push({ name: k, type, value: columnValue(k), valueKind: columnValueKind(k) })
+    }
+    return fields;
+}
+
+function ofTable<S extends Table.Schema>(name: string, table: Table<S>): Encoder.CategoryDefinition<number> {
+    return { name, fields: ofSchema(table._schema) }
+}
+
+export function getCategoryInstanceProvider(name: string, table: Table<any>): Encoder.CategoryProvider {
+    return () => {
+        return {
+            data: table,
+            definition: ofTable(name, table),
+            keys: () => Iterator.Range(0, table._rowCount - 1),
+            rowCount: table._rowCount
+        };
+    }
+}
diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts
index 64a18622f..f32bec34b 100644
--- a/src/mol-io/writer/cif/encoder.ts
+++ b/src/mol-io/writer/cif/encoder.ts
@@ -57,7 +57,7 @@ export interface CategoryProvider {
     (ctx: any): CategoryInstance
 }
 
-export interface CIFEncoder<T, Context> extends Encoder<T> {
+export interface CIFEncoder<T = string | Uint8Array, Context = any> extends Encoder<T> {
     startDataBlock(header: string): void,
     writeCategory(category: CategoryProvider, contexts?: Context[]): void,
     getData(): T
-- 
GitLab