aboutsummaryrefslogtreecommitdiff
path: root/server.py
diff options
context:
space:
mode:
authorhakangunturkun2020-03-02 11:57:12 -0600
committerhakangunturkun2020-03-02 11:57:12 -0600
commit9f933c092e1e36b14729f00ab838c005a7f5d1c4 (patch)
tree35b399bdcf7df914efa33440fa38361e8dcbf4c9 /server.py
parent3fa463365d0ba26dde51d031ddda06dee8e0e7ed (diff)
downloadgenecup-9f933c092e1e36b14729f00ab838c005a7f5d1c4.tar.gz
add many applications and functions for database, login, archive, search
Diffstat (limited to 'server.py')
-rwxr-xr-xserver.py416
1 files changed, 398 insertions, 18 deletions
diff --git a/server.py b/server.py
index 61921b5..9003515 100755
--- a/server.py
+++ b/server.py
@@ -1,25 +1,164 @@
#!/bin/env python3
-from flask import Flask, render_template, request, session, Response
+from flask import Flask, render_template, request, session, Response, redirect, url_for, flash
+from flask_sqlalchemy import SQLAlchemy
+import json
+import shutil
+from flask import jsonify
+from datetime import datetime
+import bcrypt
import tempfile
import random
import string
from ratspub import *
import time
import os
+import re
+import pytz
app=Flask(__name__)
app.config['SECRET_KEY'] = '#DtfrL98G5t1dC*4'
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///userspub.sqlite'
+db = SQLAlchemy(app)
+
+# the sqlite database
+class users(db.Model):
+ __tablename__='user'
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), nullable=False)
+ email = db.Column(db.String(80), unique=True, nullable=False)
+ password = db.Column(db.String(128), nullable=False)
+ date_created = db.Column(db.DateTime, default=datetime.utcnow)
@app.route("/")
def root():
return render_template('index.html')
+@app.route("/login", methods=["POST", "GET"])
+def login():
+ email = None
+ if request.method == "POST":
+ email = request.form['email']
+ password = request.form['password']
+ found_user = users.query.filter_by(email=email).first()
+ if (found_user and (bcrypt.checkpw(password.encode('utf8'), found_user.password))):
+ session['email'] = found_user.email
+ session['name'] = found_user.name
+ session['id'] = found_user.id
+ else:
+ flash("Invalid username or password!", "loginout")
+ return render_template('signup.html')
+ flash("Login Succesful!", "loginout")
+ return render_template('index.html')
+
+@app.route("/signup", methods=["POST", "GET"])
+def signup():
+ if request.method == "POST":
+ name = request.form['name']
+ email = request.form['email']
+ password = request.form['password']
+ found_user = users.query.filter_by(email=email).first()
+ if (found_user and (bcrypt.checkpw(password.encode('utf8'), found_user.password)==False)):
+ flash(f"Already registered, but wrong password!", "loginout")
+ return render_template('signup.html')
+ session['email'] = email
+ session['name'] = name
+ password = bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt())
+ user = users(name=name, email=email, password = password)
+ if found_user:
+ session['email'] = found_user.email
+ session['id'] = found_user.id
+ found_user.name = name
+ db.session.commit()
+ else:
+ db.session.add(user)
+ db.session.commit()
+ newuser = users.query.filter_by(email=session['email']).first()
+ session['id'] = newuser.id
+ flash(f"Login Succesful!", "loginout")
+ return render_template('index.html')
+ else:
+ if 'email' in session:
+ flash("Already Logged In!")
+ return render_template('index.html')
+ return render_template('signup.html')
+
+@app.route("/signin", methods=["POST", "GET"])
+def signin():
+ email = None
+ if request.method == "POST":
+ email = request.form['email']
+ password = request.form['password']
+ found_user = users.query.filter_by(email=email).first()
+ if (found_user and (bcrypt.checkpw(password.encode('utf8'), found_user.password))):
+ session['email'] = found_user.email
+ session['name'] = found_user.name
+ session['id'] = found_user.id
+ flash(f"Login Succesful!", "loginout")
+ return render_template('index.html')
+ else:
+ flash(f"Invalid username or password!", "loginout")
+ return render_template('signup.html')
+ return render_template('signin.html')
+
+# change password
+@app.route("/<nm_passwd>", methods=["POST", "GET"])
+def profile(nm_passwd):
+ try:
+ if "_" in str(nm_passwd):
+ user_name = str(nm_passwd).split("_")[0]
+ user_passwd = str(nm_passwd).split("_")[1]
+ user_passwd = "b\'"+user_passwd+"\'"
+ found_user = users.query.filter_by(name=user_name).first()
+ if request.method == "POST":
+ password = request.form['password']
+ session['email'] = found_user.email
+ session['name'] = found_user.name
+ session['id'] = found_user.id
+ password = bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt())
+ found_user.password = password
+ db.session.commit()
+ flash(f"Your password is changed!", "loginout")
+ return render_template('index.html')
+ # remove reserved characters from the hashed passwords
+ reserved = (";", "/", "?", ":", "@", "=", "&", ".")
+ def replace_reserved(fullstring):
+ for replace_str in reserved:
+ fullstring = fullstring.replace(replace_str,"")
+ return fullstring
+ replaced_passwd = replace_reserved(str(found_user.password))
+ if replaced_passwd == user_passwd:
+ return render_template("/passwd_change.html", name=user_name)
+ else:
+ return "This url does not exist"
+ else:
+ return "This url does not exist"
+ except (AttributeError):
+ return "This url does not exist"
+
+@app.route("/logout")
+def logout():
+ if 'email' in session:
+ global user1
+ if session['name'] != '':
+ user1 = session['name']
+ else:
+ user1 = session['email']
+ flash(f"You have been logged out, {user1}", "loginout")
+ session.pop('email', None)
+ session.clear()
+ return render_template('index.html')
+
@app.route("/about")
def about():
return render_template('about.html')
@app.route('/progress')
def progress():
+ #get the type from checkbox
+ search_type = request.args.getlist('type')
+ if (search_type == []):
+ search_type = ['GWAS', 'function', 'addiction', 'drug', 'brain', 'stress', 'psychiatric']
+ session['search_type'] = search_type
# only 1-100 terms are allowed
genes=request.args.get('query')
genes=genes.replace(",", " ")
@@ -32,29 +171,88 @@ def progress():
elif len(genes)==0:
message="<span class='text-danger'>Please enter a search term </span>"
return render_template('index.html', message=message)
- # put the query in session cookie
- session['query']=genes
- # generate a unique session ID to track the results
tf_path=tempfile.gettempdir()
- session['path']=tf_path+"/tmp" + ''.join(random.choice(string.ascii_letters) for x in range(6))
+ session['path']=tf_path+"/tmp" + ''.join(random.choice(string.ascii_letters) for x in range(6))
+ # put the query in session cookie
+ session['query']=genes
return render_template('progress.html', url_in="search", url_out="cytoscape")
@app.route("/search")
def search():
genes=session['query']
+ genes_for_folder_name =""
+ if len(genes) == 1:
+ marker = ""
+ genes_for_folder_name =str(genes[0])
+ elif len(genes) == 2:
+ marker = ""
+ genes_for_folder_name =str(genes[0])+"_"+str(genes[1])
+ elif len(genes) == 3:
+ marker = ""
+ genes_for_folder_name =str(genes[0])+"_"+str(genes[1])+"_"+str(genes[2])
+ else:
+ genes_for_folder_name =str(genes[0])+"_"+str(genes[1])+"_"+str(genes[2])
+ marker="_m"
+
+ # generate a unique session ID depending on timestamp to track the results
+ timestamp = datetime.utcnow().replace(microsecond=0)
+ timestamp = timestamp.replace(tzinfo=pytz.utc)
+ timestamp = timestamp.astimezone(pytz.timezone("America/Chicago"))
+ session['timestamp'] = timestamp
+ timeextension = str(timestamp)
+ timeextension = timeextension.replace(':', '_')
+ timeextension = timeextension.replace('-', '_')
+ timeextension = timeextension.replace(' ', '_')
+ timeextension = timeextension.replace('_06_00', '')
+ user_login=0
+ #create a folder for the search
+ if ('email' in session):
+ user_login=1
+ os.makedirs("./user/"+str(session['email']+"/"+timeextension+"_0_"+genes_for_folder_name+marker),exist_ok=True)
+ session['user_folder'] = "./user/"+str(session['email'])
+ user_folder=session['user_folder']
+ session['path'] = "./user/"+str(session['email'])+"/"+timeextension+"_0_"+genes_for_folder_name+marker+"/"+timeextension
percent=round(100/(len(genes)*6),1) # 6 categories
snt_file=session['path']+"_snt"
cysdata=open(session['path']+"_cy","w+")
sntdata=open(snt_file,"w+")
zeroLinkNode=open(session['path']+"_0link","w+")
+ search_type = session['search_type']
+ #consider the types got from checkbox
+ temp_nodes = ""
+ json_nodes = "{\"data\":["
+ if ("function" in search_type):
+ temp_nodes += n0
+ json_nodes += nj0
+ if ("addiction" in search_type):
+ temp_nodes += n1
+ json_nodes += nj1
+ if ("drug" in search_type):
+ temp_nodes += n2
+ json_nodes += nj2
+ if ("brain" in search_type):
+ temp_nodes += n3
+ json_nodes += nj3
+ if ("stress" in search_type):
+ temp_nodes += n4
+ json_nodes += nj4
+ if ("psychiatric" in search_type):
+ temp_nodes += n5
+ json_nodes += nj5
+ if ("GWAS" in search_type):
+ temp_nodes += n6
+ json_nodes += nj6
+ json_nodes = json_nodes[:-2]
+ json_nodes =json_nodes+"]}"
def generate(genes, tf_name):
sentences=str()
edges=str()
- nodes=default_nodes
+ nodes = temp_nodes
progress=0
searchCnt=0
nodesToHide=str()
- for gene in genes:
+ json_edges = str()
+ for gene in genes:
gene=gene.replace("-"," ")
# report progress immediately
progress+=percent
@@ -63,37 +261,66 @@ def search():
addiction=undic(addiction_d) +") AND ("+undic(drug_d)
sent0=gene_category(gene, addiction_d, addiction, "addiction")
e0=generate_edges(sent0, tf_name)
- #
+ ej0=generate_edges_json(sent0, tf_name)
+ # drug
drug=undic(drug_d)
sent1=gene_category(gene, drug_d, drug, "drug")
progress+=percent
yield "data:"+str(progress)+"\n\n"
e1=generate_edges(sent1, tf_name)
- #
+ ej1=generate_edges_json(sent1, tf_name)
+ # function
function=undic(function_d)
sent2=gene_category(gene, function_d, function, "function")
progress+=percent
yield "data:"+str(progress)+"\n\n"
e2=generate_edges(sent2, tf_name)
+ ej2=generate_edges_json(sent2, tf_name)
# brain has its own query terms that does not include the many short acronyms
sent3=gene_category(gene, brain_d, brain_query_term, "brain")
progress+=percent
e3=generate_edges(sent3, tf_name)
+ ej3=generate_edges_json(sent3, tf_name)
# stress
stress=undic(stress_d)
sent4=gene_category(gene, stress_d, stress, "stress")
progress+=percent
yield "data:"+str(progress)+"\n\n"
e4=generate_edges(sent4, tf_name)
+ ej4=generate_edges_json(sent4, tf_name)
# psychiatric
psychiatric=undic(psychiatric_d)
sent5=gene_category(gene, psychiatric_d, psychiatric, "psychiatric")
progress+=percent
yield "data:"+str(progress)+"\n\n"
e5=generate_edges(sent5, tf_name)
- # gwas
- e6=searchArchived('gwas', gene)
- geneEdges=e0+e1+e2+e3+e4+e5+e6
+ ej5=generate_edges_json(sent5, tf_name)
+ # GWAS
+ e6=searchArchived('GWAS', gene, 'cys')
+ ej6=searchArchived('GWAS', gene , 'json')
+ #consider the types got from checkbox
+ geneEdges = ""
+ if ("addiction" in search_type):
+ geneEdges += e0
+ json_edges += ej0
+ if ("drug" in search_type):
+ geneEdges += e1
+ json_edges += ej1
+ if ("function" in search_type):
+ geneEdges += e2
+ json_edges += ej2
+ if ("brain" in search_type):
+ geneEdges += e3
+ json_edges += ej3
+ if ("stress" in search_type):
+ geneEdges += e4
+ json_edges += ej4
+ if ("psychiatric" in search_type):
+ geneEdges += e5
+ json_edges += ej5
+ if ("GWAS" in search_type):
+ geneEdges += e6
+ json_edges += ej6
## there is a bug here. zero link notes are not excluded anymore
if len(geneEdges) >1:
edges+=geneEdges
@@ -113,16 +340,157 @@ def search():
progress=100
sntdata.write(sentences)
sntdata.close()
- cysdata.write(nodes+edges)
+ cysdata.write(nodes+edges)
cysdata.close()
zeroLinkNode.write(nodesToHide)
zeroLinkNode.close()
yield "data:"+str(progress)+"\n\n"
+ #edges in json format
+ json_edges="{\"data\":["+json_edges
+ json_edges = json_edges[:-2]
+ json_edges =json_edges+"]}"
+ #write edges to txt file in json format
+ with open("json_edges.txt", 'w') as edgesjson:
+ edgesjson.write(json_edges)
+ #write edges to txt file in json format also in user folder
+ if (user_login == 1):
+ with open(user_folder+"/"+timeextension+"_0_"+genes_for_folder_name+marker+"/json_edges.txt", "w") as temp_file_edges:
+ temp_file_edges.write(json_edges)
+ #write nodes to txt file in json format
+ with open("json_nodes.txt", 'w') as nodesjson:
+ #if (userlogin) == 1:
+ nodesjson.write(json_nodes)
+ #write nodes to txt file in json format also in user folder
+ if ('email' in session):
+ with open("./user/"+str(session['email'])+"/"+timeextension+"_0_"+genes_for_folder_name+marker+"/json_nodes.txt", "w") as temp_file_nodes:
+ temp_file_nodes.write(json_nodes)
return Response(generate(genes, snt_file), mimetype='text/event-stream')
+@app.route("/tableview")
+def tableview():
+ with open("json_nodes.txt") as jsonfile:
+ jnodes = json.load(jsonfile)
+ with open("json_edges.txt") as edgesjsonfile:
+ jedges = json.load(edgesjsonfile)
+ genename=session['query']
+ if len(genename)>3:
+ genename = genename[0:3]
+ added = ",..."
+ else:
+ added = ""
+ gene_name = str(genename)[1:]
+ gene_name=gene_name[:-1]
+ gene_name=gene_name.replace("'","")
+ gene_name = gene_name+added
+ num_gene = gene_name.count(',')+1
+ message3="<b> Notes: </b><li> Click on the abstract count to read sentences linking the keyword and the gene. <li> Click on a gene to search its relations with top 200 addiction genes. <li> Click on a keyword to see the terms included in the search. <li>View the results in <a href='cytoscape'><b> a graph.</b></a>"
+ return render_template('tableview.html', num_gene=num_gene,session_path = session['path'], jedges=jedges, jnodes=jnodes,gene_name=gene_name, message3=message3)
+
+@app.route("/tableview0")
+def tableview0():
+ with open("json_nodes.txt") as jsonfile:
+ jnodes = json.load(jsonfile)
+ with open("json_edges.txt") as edgesjsonfile:
+ jedges = json.load(edgesjsonfile)
+ genename=session['query']
+ if len(genename)>3:
+ genename = genename[0:3]
+ added = ",..."
+ else:
+ added = ""
+ gene_name = str(genename)[1:]
+ gene_name=gene_name[:-1]
+ gene_name=gene_name.replace("'","")
+ gene_name = gene_name+added
+ num_gene = gene_name.count(',')+1
+ message4="<b> Notes: </b><li> These are the words that have <b>zero</b> abstract counts. <li>View all the results in <a href='cytoscape'><b> a graph.</b></a>"
+ return render_template('tableview0.html', num_gene=num_gene,session_path = session['path'], jedges=jedges, jnodes=jnodes,gene_name=gene_name, message4=message4)
+
+@app.route("/userarchive")
+def userarchive():
+ if os.path.exists("./user/"+str(session['email'])) == False:
+ flash("Search history doesn't exist!")
+ return render_template('index.html')
+ if ('email' in session):
+ session['user_folder'] = "./user/"+str(session['email'])
+ session_id=session['id']
+ def sorted_alphanumeric(data):
+ convert = lambda text: int(text) if text.isdigit() else text.lower()
+ alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
+ return sorted(data, key=alphanum_key)
+ dirlist = sorted_alphanumeric(os.listdir(session['user_folder']))
+ folder_list = []
+ directory_list = []
+ gene_list=[]
+ for filename in dirlist:
+ folder_list.append(filename)
+ gene_name = filename.split('_0_')[1]
+ if gene_name[-2:] == '_m':
+ gene_name = gene_name[:-2]
+ gene_name = gene_name + ", ..."
+ gene_name = gene_name.replace('_', ', ')
+ gene_list.append(gene_name)
+ gene_name=""
+ filename=filename[0:4]+"-"+filename[5:7]+"-"+filename[8:13]+":"+filename[14:16]+":"+filename[17:19]
+ directory_list.append(filename)
+ len_dir = len(directory_list)
+ message3="<b> Note: </b><li> Click on the Date/Time to view archived results. <li>The Date/Time are based on US Central time zone. "
+ return render_template('userarchive.html', len_dir=len_dir, gene_list = gene_list, folder_list=folder_list, directory_list=directory_list, session_id=session_id, message3=message3)
+
+# delete this search
+@app.route('/remove', methods=['GET', 'POST'])
+def remove():
+ remove_folder = request.args.get('remove_folder')
+ shutil.rmtree("./user/"+str(session['email']+"/"+remove_folder), ignore_errors=True)
+ return redirect(url_for('userarchive'))
+
+@app.route('/date', methods=['GET', 'POST'])
+def date():
+ select_date = request.args.get('selected_date')
+ #open the cache folder for the user
+ tf_path="./user"
+ if ('email' in session):
+ time_extension = str(select_date)
+ time_extension = time_extension.split('_0_')[0]
+ time_extension = time_extension.replace(':', '_')
+ time_extension = time_extension.replace('-', '_')
+ session['path'] = tf_path+"/"+str(session['email'])+"/"+select_date+"/"+time_extension
+ session['user_folder'] = tf_path+"/"+str(session['email'])
+ else:
+ tf_path=tempfile.gettempdir()
+ session['path']=tf_path+"/tmp" + ''.join(random.choice(string.ascii_letters) for x in range(6))
+ with open(tf_path+"/"+str(session['email'])+"/"+select_date+"/json_edges.txt", "r") as archive_file:
+ with open("json_edges.txt", "w") as temp_file:
+ for line in archive_file:
+ temp_file.write(line)
+ with open(tf_path+"/"+str(session['email'])+"/"+select_date+"/json_nodes.txt", "r") as archive_file:
+ with open("json_nodes.txt", "w") as temp_file:
+ for line in archive_file:
+ temp_file.write(line)
+ with open("json_nodes.txt", "r") as jsonfile:
+ jnodes = json.load(jsonfile)
+ with open("json_edges.txt", "r") as edgesjsonfile:
+ jedges = json.load(edgesjsonfile)
+ gene_list=[]
+ for p in jedges['data']:
+ if p['source'] not in gene_list:
+ gene_list.append(p['source'])
+ if len(gene_list)>3:
+ gene_list = gene_list[0:3]
+ added = ",..."
+ else:
+ added = ""
+ gene_name = str(gene_list)[1:]
+ gene_name=gene_name[:-1]
+ gene_name=gene_name.replace("'","")
+ gene_name = gene_name+added
+ num_gene = gene_name.count(',')+1
+ message3="<b> Notes: </b><li>Click on the keywords to see the indicated number of abstracts <li> Click on a gene to search its relations with top 200 addiction genes<li>Click on a keyword to see the terms included in the search<li>Hover your pointer over a node will hide other links <li>Nodes can be moved around for better visibility, reload the page will restore the original layout<li> View the results in <a href='cytoscape'><b>a graph.</b></a>"
+ return render_template('tableview.html', title='', date=select_date, num_gene=num_gene,session_path = session['path'], jedges=jedges, jnodes=jnodes,gene_name=gene_name, message3=message3)
+
@app.route('/cytoscape')
def cytoscape():
- message2="<b> This <i>Genes vs Keywords</i> graph is interactive: </b><li>Click on a line to see the indicated number of sentences <li> Click on a gene to search its relations with top 200 addiction genes<li>Click on a keyword to see the terms included in the search<li>Hover your pointer over a node will hide other links <li>Nodes can be moved around for better visibility, reload the page will restore the original layout<p>"
+ message2="<b> Notes: </b><li>Click on a line to see the indicated number of abstracts <li> Click on a gene to search its relations with top 200 addiction genes<li>Click on a keyword to see the terms included in the search<li>Hover your pointer over a node will hide other links <li>Nodes can be moved around for better visibility, reload the page will restore the original layout<li>View the results in <a href='tableview'><b>a table. </b></a>"
with open(session['path']+"_cy","r") as f:
elements=f.read()
with open(session['path']+"_0link","r") as z:
@@ -133,23 +501,33 @@ def cytoscape():
@app.route("/sentences")
def sentences():
+ pmid_temp=""
+ pmid_list=[]
edge=request.args.get('edgeID')
(tf_name, gene0, cat0)=edge.split("|")
- out="<h3>"+gene0 + " and " + cat0 + "</h3><hr>\n"
+ out3=""
with open(tf_name, "r") as df:
all_sents=df.read()
for sent in all_sents.split("\n"):
if len(sent.strip())!=0:
(gene,nouse,cat, pmid, text)=sent.split("\t")
- if (gene.upper() == gene0.upper() and cat.upper() == cat0.upper()) :
- out+= "<li> "+ text + " <a href=\"https://www.ncbi.nlm.nih.gov/pubmed/?term=" + pmid +"\" target=_new>PMID:"+pmid+"<br></a>"
+ if (gene.upper() == gene0.upper() and cat.upper() == cat0.upper() and (pmid+cat0 not in pmid_list)) :
+ out3+= "<li> "+ text + " <a href=\"https://www.ncbi.nlm.nih.gov/pubmed/?term=" + pmid +"\" target=_new>PMID:"+pmid+"<br></a>"
+ pmid_temp = pmid
+ pmid_list.append(pmid+cat0)
+ out1="<h3>"+gene0 + " and " + cat0 + "</h3><hr>\n"
+ if len(pmid_list)>1:
+ out2 = str(len(pmid_list)) + ' sentences in ' + str(len(pmid_list)) + ' studies' "<hr>\n"
+ else:
+ out2 = str(len(pmid_list)) + ' sentence in ' + str(len(pmid_list)) + ' study' "<hr>\n"
+ out= out1+ out2 +out3
return render_template('sentences.html', sentences="<ol>"+out+"</ol><p>")
## show the cytoscape graph for one gene from the top gene list
@app.route("/showTopGene")
def showTopGene():
query=request.args.get('topGene')
- nodesEdges=searchArchived('topGene',query)
+ nodesEdges=searchArchived('topGene',query, 'cys')
message2="<li><strong>"+query + "</strong> is one of the top addiction genes. <li> An archived search is shown. Click on the blue circle to update the results and include keywords for brain region and gene function. <strong> The update may take a long time to finish.</strong> "
return render_template("cytoscape.html", elements=nodesEdges, message="Top addiction genes", message2=message2)
@@ -176,6 +554,7 @@ def gene_gene():
os.system("esearch -db pubmed -query \"" + query + "\" | efetch -format uid |sort >" + tmp_ggPMID)
abstracts=os.popen("comm -1 -2 topGene_uniq.pmid " + tmp_ggPMID + " |fetch-pubmed -path "+pubmed_path+ " | xtract -pattern PubmedArticle -element MedlineCitation/PMID,ArticleTitle,AbstractText|sed \"s/-/ /g\"").read()
os.system("rm "+tmp_ggPMID)
+ #abstracts = os.popen("esearch -db pubmed -query " + query + " | efetch -format uid |fetch-pubmed -path "+ pubmed_path + " | xtract -pattern PubmedArticle -element MedlineCitation/PMID,ArticleTitle,AbstractText|sed \"s/-/ /g\"").read()
progress=10
yield "data:"+str(progress)+"\n\n"
topGenes=dict()
@@ -249,4 +628,5 @@ def top150genes():
return render_template("topAddictionGene.html")
if __name__ == '__main__':
+ db.create_all()
app.run(debug=True, port=4200)