-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomputeValues.py
More file actions
428 lines (379 loc) · 18.5 KB
/
computeValues.py
File metadata and controls
428 lines (379 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
import json
import dateparser
import consts
from datetime import datetime, date, timedelta
from maps import cc_url_mapping, marc_language_overrides
from urllib.parse import quote
import pycountry
# ============================================================
# Class Name: etdcomputeValues
# Description:
# Purpose of this class is to encapsulate functionlity to build transformed data value
# to send to the various destination systems.
#
# Attributes:
# packageId (int): Id of the ETD in packages table.
#
# Methods:
# saveComputedValues():
# Performs transformations and save in DB.
# method_two():
# Describe what this method does.
#
# Usage:
# x = etdcomputeValues(packageId)
# x.saveComputedValues()
#
# Notes:
# - Normally this functionality is used once for an ETD.
# - It is possible to set ETD in recompute state; usually
# - done after compute bug fix or functionality change
# ============================================================
class etdcomputeValues:
_xmlAttrs = None
_packageId = None
_compAttrs = {}
# ============================================================
# Load xml attributes for computation and transformation
# ============================================================
def __init__(self, packageId):
self._packageId = packageId
self._compAttrs = {}
# get all attributes and then use the already populated info as appropriate
(_, _, xmlAttrs, compAttrs) = consts.db.getAttrs(packageId)
self._xmlAttrs = json.loads(xmlAttrs)
if compAttrs:
self._compAttrs = json.loads(compAttrs)
# ============================================================
# Update to publication date when xml is populated with that
# ============================================================
def computeDecisionDate(self):
print("Compute all dates - acceptance and decision dates")
# get the decision date and use that as publication date
if "agreement_decision_date" in self._xmlAttrs and self._xmlAttrs["agreement_decision_date"]:
decision_date = self._xmlAttrs["agreement_decision_date"]
elif "comp_date" in self._xmlAttrs and self._xmlAttrs["comp_date"]:
decision_date = self._xmlAttrs["comp_date"]
else:
decision_date = self._xmlAttrs["accept_date"]
decision = dateparser.parse(decision_date)
if decision:
decision = decision.date()
else:
decision = date.today()
# if data is Jan 1st - change to Dec 31st
if decision.month == 1 and decision.day == 1:
decision = datetime.date(decision.year, 12, 31)
return decision
# ============================================================
# Find decision data and save pub date and year
# ============================================================
def computeDates(self):
print("compute publication date and year")
decision = self.computeDecisionDate()
self._compAttrs["pub_date"] = decision.strftime('%Y-%m-%d')
self._compAttrs["pub_year"] = decision.strftime('%Y')
return decision
# ============================================================
# Process various fields to find embargo date
# ============================================================
def computeIfEmbargoed(self):
self._compAttrs["isEmbargoed"] = False
self._compAttrs["isPermEmbargoed"] = False
decision = self.computeDates()
# UCSB specific rule
if self._xmlAttrs["inst_code"] == "0035":
if self._xmlAttrs["access_option"] != "Open access":
self._compAttrs["isPermEmbargoed"] = True
if self._xmlAttrs["delayed_release"] == "never deliver":
self._compAttrs["isPermEmbargoed"] = True
if self._compAttrs["isPermEmbargoed"]:
self._compAttrs["isEmbargoed"] = True
return
if self._xmlAttrs["embargo_code"] == '0':
# not embargoed
return
end_date = None
if self._xmlAttrs["embargo_code"] == '1' or self._xmlAttrs["delayed_release"] == '6 months':
end_date = decision + timedelta(days=180)
if self._xmlAttrs["embargo_code"] == '2' or self._xmlAttrs["delayed_release"] == '1 years':
end_date = decision + timedelta(days=365)
if self._xmlAttrs["embargo_code"] == '3' or self._xmlAttrs["delayed_release"] == '2 years':
end_date = decision + timedelta(days=730)
if self._xmlAttrs["embargo_code"] == '4':
end_date = dateparser.parse(self._xmlAttrs["sales_restriction_remove"])
if end_date:
end_date = end_date.date()
else:
self._compAttrs["isPermEmbargoed"] = True
self._compAttrs["isEmbargoed"] = True
self._compAttrs["embargodate"] = "2999-12-31"
if end_date:
self._compAttrs["embargodate"] = end_date.strftime('%Y-%m-%d')
self._compAttrs["isEmbargoed"] = True
return
# ============================================================
# Default language is en. The language code in xml is two letter code
# Need to determine three letter code for downstream systems
# ============================================================
def getLanguage(self):
lang = 'en' # default
if self._xmlAttrs["language"]:
lang = self._xmlAttrs["language"]
# set Language only if non-English is specified
if lang != 'en':
language = pycountry.languages.get(alpha_2=lang)
if language:
self._compAttrs["languages"] = language.name
# MARC expects older three letter code in some case. Override if such case is detected
if lang in marc_language_overrides:
return marc_language_overrides[lang]
return language.alpha_3
return 'eng'
# ============================================================
# This is record info that becomes part of MARC record
# The field implementation is based on input from RM team
# ============================================================
def computeRecInfo(self):
# 240507s2021\\\\cau|||||obm\\||||\||eng\d
# date1 when the record was generated
# year of ETD publication
# {date}s{year}\\\\cau|||||obm\\||||\||{lang}\d
# correct: 250528s2025####cau|||||obm|||||| ||eng|d
# check: 250627s2025####cau|||||obm|||||| ||eng|d
# incorrect: 250603s2025 cau|||||obm |||| ||eng d
print("create record info")
date = datetime.now().strftime('%y%m%d')
# xml language need to convert en to eng
lang = self.getLanguage()
self._compAttrs["lang"] = lang
self._compAttrs["recinfo"] = f'{date}s{self._compAttrs["pub_year"]} cau|||||obm |||| ||{lang}|d'
return
# ============================================================
# Title is split into main title and sub title.
# ============================================================
def splitTile(self, title):
# need to split at : but only is it is not in middle of bracket
maintitle = title
subtitle = None
bracket_level = 0
current = []
for c in title:
if c == ":" and bracket_level == 0:
# just need one split
maintitle = "".join(current)
subtitle = title[len(maintitle)+1:]
return maintitle, subtitle.strip()
else:
if c == "{" or c == "(" or c == "[":
bracket_level += 1
elif c == "}" or c == ")" or c == "]":
bracket_level -= 1
current.append(c)
return maintitle, subtitle
# ============================================================
# Need to split title and also find out the code to use in Marc
# ============================================================
def computeTitleComps(self):
print("break the title by colon")
title = self._xmlAttrs["title"]
maintitle, subtitle = self.splitTile(title)
# add additional space and colon in case of a split
if subtitle:
maintitle = maintitle.strip() + " :"
subtitle = subtitle.strip() + ' /' # need to add for marc
else:
maintitle = maintitle.strip() + ' /' # need to add for marc
titleIndicator = '0'
if title.startswith("The "):
titleIndicator = '4'
if title.startswith("An "):
titleIndicator = '3'
if title.startswith("A "):
titleIndicator = '2'
self._compAttrs["maintitle"] = maintitle
self._compAttrs["subtitle"] = subtitle
self._compAttrs["titleIndicator"] = titleIndicator
return
# ============================================================
# Build campus information based on inst code in xml
# ============================================================
def computeCampusInfo(self):
print("get campus location and name from db")
schoolcode = self._xmlAttrs["inst_code"]
self._compAttrs["campuslocation"] = consts.campusinfo[schoolcode].instloc + ", California :"
self._compAttrs["campusname"] = "University of California, " + consts.campusinfo[schoolcode].namesuffix
self._compAttrs["campusshort"] = consts.campusinfo[schoolcode].code.lower()
self._compAttrs["control655"] = consts.campusinfo[schoolcode].nameinmarc
self._compAttrs["merrittbucket"] = self._compAttrs["campusshort"] + "_lib_etd"
return
# ============================================================
# Package the advisor and committee member info
# ============================================================
def computeNotes(self):
# need to create notes based on xml info about advisors
advisors = "Advisor(s): "
members = "Committee members: "
if len(self._xmlAttrs["advisors"]) == 0:
self._compAttrs["notes"] = None
return
for advisor in self._xmlAttrs["advisors"]:
advisors = advisors + f'{advisor["surname"]}, {advisor["fname"]}; '
for member in self._xmlAttrs["members"]:
members = members + f'{member["surname"]}, {member["fname"]}; '
#strip and add full stop if needed
notes = advisors.strip('; ')
if len(self._xmlAttrs["members"]):
notes = notes + '. ' + members.strip('; ')
self._compAttrs["notes"] = notes
return
# ============================================================
# Really simple task to join abstract lines.
# The abstract from xml joined for filling in marc
# ============================================================
def computeAbstract(self):
self._compAttrs["abstract"] = " ".join(self._xmlAttrs["abstractLines"])
# ============================================================
# Need to split title and also find out the code to use in Marc
# ============================================================
def computeAuthorsAdvisor(self):
# need to compile the list from xml information - lastname, first middle format
# need a comma at the end for marc record sake
self._compAttrs["advisor"] = []
for advisor in self._xmlAttrs["advisors"]:
name = f'{advisor["surname"]}, {advisor["fname"]}'
self._compAttrs["advisor"].append(name)
names = ""
creators = []
for author in self._xmlAttrs["authset"]:
name = f'{author["fname"]} {author["surname"]},' # note sure if it comma separated or not
creators.append(f'{author["surname"]}, {author["fname"][0]}.')
names = names + name
self._compAttrs["creators"] = ';'.join(creators)
self._compAttrs["authors"] = names.strip(',')
self._compAttrs["mainauthor"] = f'{self._xmlAttrs["authset"][0]["surname"]},{self._xmlAttrs["authset"][0]["fname"]}'
return
# ============================================================
# Dept is filled with discipline from xml.
# ============================================================
def computeDept(self):
# Start with this and see if something needs to change
self._compAttrs["dept"] = self._xmlAttrs["discipline"]
return
# ============================================================
# Authors needs to be formated in eschol specific format
# ============================================================
def getescholAuthors(self):
authors = []
for author in self._xmlAttrs["authset"]:
values = {}
values["nameParts"] = {"fname":author["fname"],"lname":author["surname"], "mname": author["middle"]}
values["email"] = author["email"]
values["orcid"] = author["orcid"]
authors.append(values)
return authors
# ============================================================
# These are the local ids set for eschol submission
# ============================================================
def getescholIds(self):
localIds = []
#API doesn't allow ark
#localIds.append({"id":self._compAttrs["merrittark"], "scheme":"ARK"})
# what other ids can I provide
localIds = {"id": self._xmlAttrs["external_id"], "scheme":"OTHER_ID", "subScheme":"proquest"}
return localIds
# ============================================================
# Determine the eschol units the ETD needs to be part of.
# Current logic is campus etd unit. There is possiblity of
# extending this to add additional units once mapping from
# xml info to department unit id is formulated.
# ============================================================
def getescholunits(self):
units = []
# get unit from campus settings
units.append(self._compAttrs["campusshort"] + "_etd")
# inst_contact in xml can be used to determine other units the article should go to
return units
# ============================================================
# Fill in ADVISOR contributors for eschol deposit package
# ============================================================
def getcontributors(self):
contribs = []
# authinfo["middle"]
for advisor in self._xmlAttrs["advisors"]:
nameparts = {"fname": advisor["fname"],
"lname": advisor["surname"],
"mname": advisor["middle"]}
contribs.append({"role":"ADVISOR", "nameParts": nameparts})
return contribs
# ============================================================
# The supplementary file info based on xml. The information is
# updated with file size when the files are copied to share folder
# for API to upload to eschol.
# ============================================================
def getsupplementaryFiiles(self):
if not self._xmlAttrs["attachset"]:
return None
suppFiles = []
linkbase = f"{consts.configs['base_urls.depositUrlBase']}/{self._xmlAttrs['depositfolder']}"
for attachment in self._xmlAttrs["attachset"]:
filename = attachment['name']
# encode the link name
link = f'{linkbase}/{filename}'
entry = { "file": filename,
"fetchLink": quote(link, safe=":/?=&"),
"title": attachment["descr"],
"size": "TBD" # TBD: need to get this from fileattrs
}
# os.path.getsize(file_path)
suppFiles.append(entry)
return suppFiles
# ============================================================
# Fill in cc license url based on code in xml if present
# ============================================================
def getcclicense(self):
# get the actual license if present
if "cclicense" in self._xmlAttrs and self._xmlAttrs["cclicense"]:
abbr = self._xmlAttrs["cclicense"].lower()
if abbr in cc_url_mapping:
return cc_url_mapping[abbr]
else:
print("CC lincese not found in mapping " + abbr )
return None
# ============================================================
# Compute the values needed for eschol deposit
# ============================================================
def computeEscholValues(self):
print("compute for eschol deposit json")
self._compAttrs["isPeerReviewed"] = True
link = f"{consts.configs['base_urls.depositUrlBase']}/{self._xmlAttrs['depositfolder']}/{self._xmlAttrs['binary-name']}"
self._compAttrs["contentLink"] = quote(link, safe=":/?=&")
self._compAttrs["escholauthors"] = self.getescholAuthors()
self._compAttrs["escholIds"] = self.getescholIds()
self._compAttrs["escholunits"] = self.getescholunits()
self._compAttrs["escholadvisors"] = self.getcontributors()
self._compAttrs["escholsupp"] = self.getsupplementaryFiiles()
self._compAttrs["cclicence"] = self.getcclicense()
# ============================================================
# Perform data info computation and save results in DB
# ============================================================
def saveComputedValues(self):
print("generate computed values for one record")
# get the json version of the attrs
self.computeIfEmbargoed()
self.computeRecInfo()
self.computeTitleComps()
self.computeCampusInfo()
self.computeNotes()
self.computeAuthorsAdvisor()
self.computeDept()
self.computeAbstract()
self.computeEscholValues()
# does this depend upon the degree?
self._compAttrs["genre"] = "Dissertations, Academic"
consts.db.saveComputedValues(self._packageId, json.dumps(self._compAttrs,ensure_ascii=False))
return
#x = "this is the link with space"
#print(quote(x))
# x = etdcomputeValues(17)
# x.saveComputedValues()