Coverage for control/overview.py : 69%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Overview page of contributions.
3* Country selection
4* Grouping by categories
5* Statistics
6"""
8import json
9from flask import request, make_response
11from config import Config as C, Names as N
12from control.utils import pick as G, E, NBSP, COMMA, PLUS, MIN, ONE, MINONE, S, NL, TAB
13from control.html import HtmlElements as H
16CT = C.tables
17CW = C.web
19URLS = CW.urls
20PAGE = URLS[N.info][N.url]
21PAGEX = f"""{PAGE}.tsv"""
23COL_SINGULAR = dict(
24 country=N.country,
25 assessed="stage",
26 selected="stage",
27 reviewed1="stage",
28 reviewed2="stage",
29 reviewer1="expert reviewer",
30 reviewer2="final reviewer",
31)
33COL_PLURAL = dict(
34 country=N.countries,
35 assessed="stages",
36 selected="stages",
37 reviewed1="stages",
38 reviewed2="stages",
39 reviewer1="expert reviewers",
40 reviewer2="final reviewers",
41)
43REVIEWER1 = "reviewer1"
44REVIEWER2 = "reviewer2"
45REVIEWED1 = "reviewed1"
46REVIEWED2 = "reviewed2"
47R1RANK = "r1Rank"
48R2RANK = "r2Rank"
50COLSPECS = (
51 (N.country, str),
52 (N.year, int),
53 (N.type, str),
54 (N.cost, int, """cost (€)"""),
55 (N.assessed, tuple),
56 (N.selected, bool),
57 (REVIEWER1, str),
58 (REVIEWER2, str),
59 (REVIEWED1, str),
60 (REVIEWED2, str),
61 (N.title, str),
62)
64GROUP_COLS = f"""
65 country
66 year
67 type
68 assessed
69 {REVIEWER1}
70 {REVIEWER2}
71 {REVIEWED1}
72 {REVIEWED2}
73 selected
74""".strip().split()
76SUBHEAD_X_COLS = set(
77 """
78 cost
79 title
80""".strip().split()
81)
83ASSESSED_STATUS = {
84 None: ("no assessment", "a-none"),
85 N.incomplete: ("started", "a-started"),
86 N.incompleteRevised: ("revision", "a-started"),
87 N.incompleteWithdrawn: ("withdrawn", "a-none"),
88 N.complete: ("filled-in", "a-self"),
89 N.completeRevised: ("revised", "a-self"),
90 N.completeWithdrawn: ("withdrawn", "a-none"),
91 N.submitted: ("in review", "a-inreview"),
92 N.submittedRevised: ("in review", "a-inreview"),
93 N.reviewReject: ("rejected", "a-rejected"),
94 N.reviewAccept: ("accepted", "a-accepted"),
95}
96ASSESSED_LABELS = {stage: info[0] for (stage, info) in ASSESSED_STATUS.items()}
97ASSESSED_CLASS = {stage: info[1] for (stage, info) in ASSESSED_STATUS.items()}
98ASSESSED_CLASS1 = {info[0]: info[1] for info in ASSESSED_STATUS.values()}
99ASSESSED_DEFAULT_CLASS = ASSESSED_STATUS[None][1]
100ASSESSED_RANK = {stage: i for (i, stage) in enumerate(ASSESSED_STATUS)}
102NO_REVIEW = {
103 N.incomplete,
104 N.incompleteRevised,
105 N.incompleteWithdrawn,
106 N.complete,
107 N.completeRevised,
108 N.completeWithdrawn,
109}
110IN_REVIEW = {
111 N.submitted,
112 N.submittedRevised,
113}
114ADVISORY_REVIEW = {
115 N.reviewAdviseAccept,
116 N.reviewAdviseReject,
117 N.reviewAdviseRevise,
118}
119FINAL_REVIEW = {
120 N.reviewAccept,
121 N.reviewReject,
122 N.reviewRevise,
123}
126REVIEWED_STATUS = {
127 None: ("", "r-none"),
128 "noReview": ("not reviewable", "r-noreview"),
129 "inReview": ("in review", "r-inreview"),
130 "skipReview": ("review skipped", "r-skipreview"),
131 N.reviewAdviseReject: ("rejected", "r-rejected"),
132 N.reviewAdviseAccept: ("accepted", "r-accepted"),
133 N.reviewAdviseRevise: ("revise", "r-revised"),
134 N.reviewReject: ("rejected", "r-rejected"),
135 N.reviewAccept: ("accepted", "r-accepted"),
136 N.reviewRevise: ("revise", "r-revised"),
137}
138REVIEW_LABELS = {stage: info[0] for (stage, info) in REVIEWED_STATUS.items()}
139REVIEW_CLASS = {stage: info[1] for (stage, info) in REVIEWED_STATUS.items()}
140REVIEW_CLASS1 = {info[0]: info[1] for info in REVIEWED_STATUS.values()}
141REVIEW_DEFAULT_CLASS = REVIEWED_STATUS[None][1]
142REVIEW_RANK = {stage: i for (i, stage) in enumerate(REVIEWED_STATUS)}
144ALL = """All countries"""
147class Overview:
148 def __init__(self, context):
149 self.context = context
151 types = context.types
152 self.bool3Obj = types.bool3
153 self.countryType = types.country
154 self.yearType = types.year
155 self.typeType = types.typeContribution
156 self.userType = types.user
158 def getCountry(self, country):
159 context = self.context
160 db = context.db
161 auth = context.auth
162 user = auth.user
163 countryType = self.countryType
165 self.userGroup = auth.groupRep()
166 self.myCountry = auth.countryRep()
168 userCountryId = G(user, N.country)
169 chosenCountry = None
170 chosenCountryIso = None
171 chosenCountryId = None
173 countryId = G(db.countryInv, country) if country else userCountryId
175 if countryId is not None:
176 chosenCountryId = countryId
177 countryInfo = G(db.country, chosenCountryId, default={})
178 chosenCountry = countryType.titleStr(countryInfo)
179 chosenCountryIso = G(countryInfo, N.iso)
181 self.chosenCountry = chosenCountry
182 self.chosenCountryId = chosenCountryId
183 self.chosenCountryIso = chosenCountryIso
185 def getContribs(self, bulk):
186 context = self.context
187 db = context.db
188 chosenCountryId = self.chosenCountryId
189 countryType = self.countryType
190 userType = self.userType
191 yearType = self.yearType
192 typeType = self.typeType
193 isSuperUser = self.isSuperUser
195 users = db.user
197 contribs = {}
198 for record in db.bulkContribWorkflow(chosenCountryId, bulk):
199 title = G(record, N.title)
200 contribId = G(record, N._id)
202 selected = G(record, N.selected)
203 aStage = G(record, N.aStage)
204 r2Stage = G(record, N.r2Stage)
205 if r2Stage in {N.reviewAccept, N.reviewReject}: 205 ↛ 206line 205 didn't jump to line 206, because the condition on line 205 was never true
206 aStage = r2Stage
207 score = G(record, N.score)
208 assessed = ASSESSED_STATUS[aStage][0]
209 aRank = (G(ASSESSED_RANK, aStage, default=0), score or 0)
210 if aStage != N.reviewAccept: 210 ↛ 213line 210 didn't jump to line 213, because the condition on line 210 was never false
211 score = None
213 countryRep = countryType.titleStr(G(db.country, G(record, N.country)))
214 yearRep = yearType.titleStr(G(db.year, G(record, N.year)))
215 typeRep = typeType.titleStr(G(db.typeContribution, G(record, N.type)))
216 cost = G(record, N.cost)
218 contribRecord = {
219 N._id: contribId,
220 N._cn: countryRep,
221 N.country: countryRep,
222 N.year: yearRep,
223 N.type: typeRep,
224 N.title: title,
225 N.cost: cost,
226 N.assessed: assessed,
227 N.arank: aRank,
228 N.astage: aStage,
229 N.score: score,
230 N.selected: selected,
231 }
232 if isSuperUser: 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true
233 preR1Stage = G(record, N.r1Stage)
234 noReview = aStage is None or aStage in NO_REVIEW
235 inReview = aStage in IN_REVIEW
236 advReview = preR1Stage in ADVISORY_REVIEW
237 r1Stage = (
238 "noReview"
239 if noReview
240 else preR1Stage
241 if advReview
242 else "inReview"
243 if inReview
244 else "skipReview"
245 )
246 r2Stage = (
247 "noReview"
248 if noReview
249 else "inReview"
250 if inReview
251 else G(record, N.r2Stage)
252 )
253 reviewed1 = REVIEWED_STATUS[r1Stage][0]
254 reviewed2 = REVIEWED_STATUS[r2Stage][0]
255 r1Rank = G(REVIEW_RANK, r1Stage, default=0)
256 r2Rank = G(REVIEW_RANK, r2Stage, default=0)
257 reviewer = {}
258 for kind in ("E", "F"):
259 reviewerId = G(record, getattr(N, f"reviewer{kind}"))
260 if reviewerId is None:
261 reviewer[kind] = None
262 else:
263 reviewerRecord = G(users, reviewerId)
264 reviewerRep = userType.titleStr(reviewerRecord)
265 reviewer[kind] = reviewerRep
266 contribRecord.update(
267 {
268 REVIEWER1: reviewer["E"],
269 REVIEWER2: reviewer["F"],
270 REVIEWED1: reviewed1,
271 REVIEWED2: reviewed2,
272 R1RANK: r1Rank,
273 R2RANK: r2Rank,
274 N.r1Stage: r1Stage,
275 N.r2Stage: r2Stage,
276 }
277 )
278 contribs[contribId] = contribRecord
280 self.contribs = contribs
282 def roTri(self, tri):
283 return self.bool3Obj.toDisplay(tri, markup=False)
285 def wrap(self, asTsv=False):
286 context = self.context
287 db = context.db
288 auth = context.auth
289 countryType = self.countryType
291 isSuperUser = auth.superuser()
292 self.isSuperUser = isSuperUser
293 self.isCoord = auth.coordinator()
295 colSpecs = COLSPECS
296 hiddenCols = (
297 set() if isSuperUser else {REVIEWER1, REVIEWER2, REVIEWED1, REVIEWED2}
298 )
299 groupCols = [gc for gc in GROUP_COLS if gc not in hiddenCols]
300 allGroupSet = set(groupCols)
302 accessRep = auth.credentials()[1]
304 rawBulk = request.args.get(N.bulk, E)
305 bulk = True if rawBulk else False
306 rawSortCol = request.args.get(N.sortcol, E)
307 rawReverse = request.args.get(N.reverse, E)
308 country = request.args.get(N.country, E)
309 groups = COMMA.join(
310 g for g in request.args.get(N.groups, E).split(COMMA) if g in allGroupSet
311 )
313 self.getCountry(country)
314 self.getContribs(bulk)
316 chosenCountry = self.chosenCountry
317 chosenCountryId = self.chosenCountryId
318 chosenCountryIso = self.chosenCountryIso
320 if chosenCountryId is not None:
321 colSpecs = [x for x in COLSPECS if x[0] != N.country]
322 groups = self.rmGroup(groups.split(COMMA), N.country)
323 groupCols = [x for x in groupCols if x != N.country]
325 cols = [c[0] for c in colSpecs]
326 colSet = {c[0] for c in colSpecs}
328 self.types = dict((c[0], c[1]) for c in colSpecs)
329 labels = dict((c[0], c[2] if len(c) > 2 else c[0]) for c in colSpecs)
330 sortDefault = cols[-1]
332 groupsChosen = [] if not groups else groups.split(COMMA)
333 groupSet = set(groupsChosen)
334 groupStr = ("""-by-""" if groupSet else E) + MIN.join(sorted(groupSet))
336 sortCol = sortDefault if rawSortCol not in colSet else rawSortCol
337 reverse = False if rawReverse not in {MINONE, ONE} else rawReverse == MINONE
339 self.cols = cols
340 self.labels = labels
341 self.bulk = bulk
342 self.groupCols = groupCols
343 self.sortCol = sortCol
344 self.reverse = reverse
346 material = []
347 if not asTsv:
348 material.append(H.h(3, """Country selection"""))
350 countryItems = [
351 H.a(
352 ALL,
353 (
354 f"""{PAGE}?bulk={rawBulk}&country=x&sortcol={rawSortCol}&"""
355 f"""reverse={rawReverse}&groups={groups}"""
356 ),
357 cls="c-control",
358 )
359 if chosenCountryId
360 else H.span(ALL, cls="c-focus")
361 ]
362 for (cid, countryInfo) in sorted(
363 db.country.items(), key=lambda x: G(x[1], "iso", "zz")
364 ):
365 if not G(countryInfo, N.isMember):
366 continue
367 name = countryType.titleStr(countryInfo)
368 iso = G(countryInfo, N.iso)
370 countryItems.append(
371 H.span(name, cls="c-focus")
372 if cid == chosenCountryId
373 else H.a(
374 name,
375 (
376 f"""{PAGE}?bulk={rawBulk}&country={iso}&"""
377 f"""sortcol={rawSortCol}&"""
378 f"""reverse={rawReverse}&groups={groups}"""
379 ),
380 cls="c-control",
381 )
382 )
383 material.append(H.p(countryItems, cls=N.countries))
385 groupsAvailable = sorted(allGroupSet - set(groupsChosen))
386 groupOrder = groupsChosen + [g for g in cols if g not in groupSet]
388 if not asTsv:
389 urlArgsBare = (
390 f"""country={chosenCountryIso or 'x'}&"""
391 f"""sortcol={rawSortCol}&reverse={rawReverse}"""
392 )
393 urlArgs = f"""{urlArgsBare}&bulk={rawBulk}"""
394 urlArgsBulk0 = f"""{urlArgsBare}&bulk=&groups={groups}"""
395 urlArgsBulk1 = f"""{urlArgsBare}&bulk=1&groups={groups}"""
396 urlStart1 = f"""{PAGE}?{urlArgs}"""
397 urlStart = f"""{urlStart1}&groups="""
398 availableReps = E.join(
399 H.a(
400 f"""+{g}""",
401 (f"""{urlStart}{self.addGroup(groupsChosen, g)}"""),
402 cls="g-add",
403 )
404 for g in groupsAvailable
405 )
406 chosenReps = E.join(
407 H.a(
408 f"""-{g}""",
409 f"""{urlStart}{self.rmGroup(groupsChosen, g)}""",
410 cls="g-rm",
411 )
412 for g in groupsChosen
413 )
414 clearGroups = (
415 E
416 if len(chosenReps) == 0
417 else H.iconx(
418 N.clear, urlStart1, cls="g-x", title="""clear all groups"""
419 )
420 )
421 rArgs = f"""{urlArgs}&groups={groups}"""
423 headerLine = self.ourCountryHeaders(
424 country,
425 groups,
426 asTsv,
427 groupOrder=groupOrder,
428 )
430 contribs = self.contribs
431 nContribs = len(contribs)
432 plural = E if nContribs == 1 else S
433 things = f"""{len(contribs)} contribution{plural}"""
434 origin = chosenCountry or ALL.lower()
436 if not asTsv:
437 bulkHead = "Show all" if bulk else "Show bulk imports only"
438 bulkPre = H.span("Showing bulk imports only") + NBSP if bulk else E
439 bulkUrl = f"""{PAGE}?{urlArgsBulk0 if bulk else urlArgsBulk1}"""
440 material.append(H.h(3, """Grouping"""))
441 material.append(
442 H.table(
443 [],
444 [
445 (
446 [
447 ("""available groups""", dict(cls="mtl")),
448 (availableReps, dict(cls="mtd")),
449 (NBSP, {}),
450 ],
451 {},
452 ),
453 (
454 [
455 ("""chosen groups""", dict(cls="mtl")),
456 (chosenReps, dict(cls="mtd")),
457 (clearGroups, {}),
458 ],
459 {},
460 ),
461 ],
462 cls="mt",
463 )
464 )
465 material.append(H.p([bulkPre, H.a(bulkHead, bulkUrl, cls="button small")]))
466 material.append(
467 H.h(
468 3,
469 [
470 f"""{things} from {origin}""",
471 H.a(
472 """Download as Excel""",
473 f"""{PAGEX}?{rArgs}""",
474 target="_blank",
475 cls="button large",
476 ),
477 ],
478 )
479 )
481 (thisMaterial, groupRel) = self.groupList(
482 groupsChosen,
483 chosenCountry,
484 chosenCountry,
485 asTsv,
486 )
488 if asTsv:
489 material.append(thisMaterial)
490 else:
491 material.append(H.table([headerLine], thisMaterial, cls="cc"))
492 material.append(groupRel)
494 if asTsv:
495 countryRep = (
496 "all-countries"
497 if country == "x"
498 else country
499 if country
500 else "their-country"
501 )
502 fileName = f"""dariah-{countryRep}{groupStr}-for-{accessRep}"""
503 headers = {
504 "Expires": "0",
505 "Cache-Control": "no-cache, no-store, must-revalidate",
506 "Content-Type": "text/csv",
507 "Content-Disposition": f'attachment; filename="{fileName}"',
508 "Content-Encoding": "identity",
509 }
510 tsv = f"""\ufeff{headerLine}\n{NL.join(material)}""".encode("""utf_16_le""")
511 data = make_response(tsv, headers)
512 else:
513 data = E.join(material)
515 return data
517 def groupList(
518 self,
519 groups,
520 selectedCountry,
521 chosenCountry,
522 asTsv,
523 ):
524 cols = self.cols
525 groupCols = self.groupCols
526 contribs = self.contribs
527 sortCol = self.sortCol
528 reverse = self.reverse
530 if len(groups) == 0: 530 ↛ 561line 530 didn't jump to line 561, because the condition on line 530 was never false
531 groupedList = sorted(
532 contribs.values(), key=self.contribKey(sortCol), reverse=reverse
533 )
534 if asTsv:
535 return (
536 NL.join(
537 self.formatContrib(
538 contrib,
539 None,
540 chosenCountry,
541 asTsv,
542 )
543 for contrib in groupedList
544 ),
545 E,
546 )
547 else:
548 return (
549 [
550 self.formatContrib(
551 contrib,
552 None,
553 chosenCountry,
554 asTsv,
555 )
556 for contrib in groupedList
557 ],
558 E,
559 )
561 preGroups = groups[0:-1]
562 lastGroup = groups[-1]
564 groupLen = len(groups)
565 groupSet = set(groups)
566 groupOrder = groups + [g for g in cols if g not in groupSet]
568 groupedList = {}
570 for c in contribs.values():
571 dest = groupedList
572 for g in preGroups:
573 dest = dest.setdefault(G(c, g), {})
574 dest = dest.setdefault(G(c, lastGroup), [])
575 dest.append(c)
577 material = []
578 maxGroupId = 1
579 groupRel = {}
581 def groupMaterial(gList, depth, groupValues, parentGroupId):
582 groupSet = set(groupValues.keys())
584 nonlocal maxGroupId
585 maxGroupId += 1
586 thisGroupId = maxGroupId
587 thisGroupId = COMMA.join(f"""{k}:{v}""" for (k, v) in groupValues.items())
588 groupRel.setdefault(str(parentGroupId), []).append(str(thisGroupId))
590 headIndex = len(material)
591 material.append(MIN if asTsv else ([(MIN, {})], {}))
592 nRecords = 0
593 nGroups = 0
594 cost = 0
595 if type(gList) is list:
596 for rec in sorted(
597 (
598 {k: v for (k, v) in list(d.items()) if k not in groupValues}
599 for d in gList
600 ),
601 key=self.contribKey(sortCol),
602 reverse=reverse,
603 ):
604 nRecords += 1
605 nGroups += 1
606 cost += G(rec, N.cost) or 0
607 material.append(
608 self.formatContrib(
609 rec,
610 thisGroupId,
611 chosenCountry,
612 asTsv,
613 groupOrder=groupOrder,
614 hide=True,
615 )
616 )
617 else:
618 newGroup = groups[depth]
619 for groupValue in sorted(
620 gList.keys(),
621 key=self.contribKey(newGroup, individual=True),
622 reverse=reverse,
623 ):
624 nGroups += 1
625 newGroupValues = {}
626 newGroupValues.update(groupValues)
627 newGroupValues[newGroup] = groupValue
628 (nRecordsG, costG) = groupMaterial(
629 gList[groupValue],
630 depth + 1,
631 newGroupValues,
632 thisGroupId,
633 )
634 nRecords += nRecordsG
635 cost += costG
636 groupValuesT = {}
637 if depth > 0:
638 thisGroup = groups[depth - 1]
639 groupValuesT[thisGroup] = groupValues[thisGroup]
640 # groupValuesT.update(groupValues)
641 groupValuesT[N.cost] = cost
642 groupValuesT[N.title] = self.colRep(N.contribution, nRecords)
643 groupValuesT[N._cn] = G(groupValues, N.country)
644 if depth == 0:
645 for g in groupCols + [N.title]:
646 label = selectedCountry if g == N.country else N.all
647 controls = (
648 self.expandAcontrols(g) if g in groups or g == N.title else E
649 )
650 groupValuesT[g] = label if asTsv else f"""{label} {controls}"""
651 material[headIndex] = self.formatContrib(
652 groupValuesT,
653 parentGroupId,
654 chosenCountry,
655 asTsv,
656 groupOrder=groupOrder,
657 groupSet=groupSet,
658 subHead=True,
659 allHead=depth == 0,
660 groupLen=groupLen,
661 depth=depth,
662 thisGroupId=thisGroupId,
663 nGroups=nGroups,
664 )
665 return (nRecords, cost)
667 groupMaterial(groupedList, 0, {}, 1)
668 return (
669 NL.join(material) if asTsv else material,
670 H.script(f"""var groupRel = {json.dumps(groupRel)}"""),
671 )
673 def formatContrib(
674 self,
675 contrib,
676 groupId,
677 chosenCountry,
678 asTsv,
679 groupOrder=None,
680 groupSet=set(),
681 subHead=False,
682 allHead=False,
683 groupLen=None,
684 depth=None,
685 thisGroupId=None,
686 nGroups=None,
687 hide=False,
688 ):
689 cols = self.cols
690 isSuperUser = self.isSuperUser
692 if groupOrder is None: 692 ↛ 694line 692 didn't jump to line 694, because the condition on line 692 was never false
693 groupOrder = cols
694 contribId = G(contrib, N._id)
695 (assessedLabel, assessedClass) = self.wrapStatus(contrib, subHead=subHead)
696 if allHead: 696 ↛ 697line 696 didn't jump to line 697, because the condition on line 696 was never true
697 selected = G(contrib, N.selected) or E
698 if asTsv:
699 selected = self.valTri(selected)
700 assessedClass = E
701 else:
702 selected = G(contrib, N.selected)
703 selected = (
704 (self.valTri(selected) if asTsv else self.roTri(selected))
705 if N.selected in contrib
706 else E
707 )
708 rawTitle = G(contrib, N.title) or E
709 title = (
710 rawTitle
711 if asTsv
712 else rawTitle
713 if subHead
714 else H.a(
715 f"""{rawTitle or "? missing title ?"}""",
716 f"""/{N.contrib}/{N.item}/{contribId}""",
717 )
718 if N.title in contrib
719 else E
720 )
722 values = {
723 N.country: G(contrib, N.country) or E,
724 N.year: G(contrib, N.year) or E,
725 N.type: G(contrib, N.type) or E,
726 N.cost: self.euro(G(contrib, N.cost)),
727 N.assessed: assessedLabel,
728 N.selected: selected,
729 N.title: title,
730 }
731 if isSuperUser: 731 ↛ 732line 731 didn't jump to line 732, because the condition on line 731 was never true
732 (r1Label, r1Class) = self.wrapStatus(contrib, subHead=subHead, kind="1")
733 (r2Label, r2Class) = self.wrapStatus(contrib, subHead=subHead, kind="2")
734 if allHead:
735 r1Class = E
736 r2Class = E
737 reviewer1 = G(contrib, REVIEWER1)
738 reviewer2 = G(contrib, REVIEWER2)
739 values.update(
740 {
741 REVIEWER1: reviewer1 or E,
742 REVIEWER2: reviewer2 or E,
743 REVIEWED1: r1Label,
744 REVIEWED2: r2Label,
745 }
746 )
747 recCountry = G(contrib, N._cn) or G(values, N.country)
748 if depth is not None: 748 ↛ 749line 748 didn't jump to line 749, because the condition on line 748 was never true
749 xGroup = groupOrder[depth] if depth == 0 or depth < groupLen else N.title
750 xName = N.contribution if xGroup == N.title else xGroup
751 xRep = self.colRep(xName, nGroups)
752 values[xGroup] = (
753 xRep
754 if asTsv
755 else (
756 f"""{self.expandControls(thisGroupId, True)} {xRep}"""
757 if xGroup == N.title
758 else f"""{values[xGroup]} ({xRep}) {self.expandControls(thisGroupId)}"""
759 if depth > 0
760 else f"""{values[xGroup]} ({xRep}) {self.expandControls(thisGroupId)}"""
761 )
762 )
763 if not asTsv: 763 ↛ 769line 763 didn't jump to line 769, because the condition on line 763 was never false
764 classes = {col: f"c-{col}" for col in groupOrder}
765 classes["assessed"] += f" {assessedClass}"
766 if isSuperUser: 766 ↛ 767line 766 didn't jump to line 767, because the condition on line 766 was never true
767 classes[REVIEWED1] = r1Class
768 classes[REVIEWED2] = r2Class
769 if asTsv: 769 ↛ 770line 769 didn't jump to line 770, because the condition on line 769 was never true
770 columns = TAB.join(
771 self.disclose(values, col, recCountry) or E for col in groupOrder
772 )
773 else:
774 columns = [
775 (
776 self.disclose(values, col, recCountry),
777 dict(
778 cls=(
779 f"{classes[col]} "
780 + self.subHeadClass(col, groupSet, subHead, allHead)
781 )
782 ),
783 )
784 for col in groupOrder
785 ]
786 if not asTsv: 786 ↛ 791line 786 didn't jump to line 791, because the condition on line 786 was never false
787 hideRep = " hide" if hide else E
788 displayAtts = (
789 {} if groupId is None else dict(cls=f"dd{hideRep}", gid=groupId)
790 )
791 return columns if asTsv else (columns, displayAtts)
793 def contribKey(self, col, individual=False):
794 types = self.types
796 colType = types[col]
798 def makeKey(contrib):
799 if col == N.assessed: 799 ↛ 800line 799 didn't jump to line 800, because the condition on line 799 was never true
800 return G(contrib, N.arank, default=(E, 0))
801 elif col == REVIEWED1: 801 ↛ 802line 801 didn't jump to line 802, because the condition on line 801 was never true
802 return G(contrib, R1RANK, default=0)
803 elif col == REVIEWED2: 803 ↛ 804line 803 didn't jump to line 804, because the condition on line 803 was never true
804 return G(contrib, R2RANK, default=0)
805 value = G(contrib, col)
806 if value is None: 806 ↛ 807line 806 didn't jump to line 807, because the condition on line 806 was never true
807 return E if colType is str else 0
808 if colType is str: 808 ↛ 810line 808 didn't jump to line 810, because the condition on line 808 was never false
809 return value.lower()
810 if colType is bool:
811 return 1 if value else -1
812 if colType is int:
813 return 0 if type(value) is str else value
814 return E
816 def makeKeyInd(value):
817 if col == N.assessed:
818 return value or E
819 if value is None:
820 return E if colType is str else 0
821 if colType is str:
822 return value.lower()
823 if colType is bool:
824 return 1 if value else -1
825 if colType is int:
826 return 0 if type(value) is str else value
827 return E
829 return makeKeyInd if individual else makeKey
831 def ourCountryHeaders(self, country, groups, asTsv, groupOrder=None):
832 cols = self.cols
833 labels = self.labels
834 bulk = self.bulk
835 sortCol = self.sortCol
836 reverse = self.reverse
838 if groupOrder is None: 838 ↛ 839line 838 didn't jump to line 839, because the condition on line 838 was never true
839 groupOrder = cols
841 if asTsv:
842 headers = E
843 sep = E
844 for col in groupOrder:
845 label = labels[col]
846 colControl = label
847 headers += f"""{sep}{colControl}"""
848 sep = TAB
850 else:
851 headers = []
852 dirClass = N.desc if reverse else N.asc
853 dirIcon = N.adown if reverse else N.aup
854 rawBulk = "1" if bulk else E
855 urlStart = f"""{PAGE}?bulk={rawBulk}&country={country}&groups={groups}"""
856 for col in groupOrder:
857 isSorted = col == sortCol
858 thisClass = f"c-{col}"
859 icon = E
860 if isSorted:
861 thisClass += f" {dirClass}"
862 nextReverse = not reverse
863 icon = H.iconx(dirIcon)
864 else:
865 nextReverse = False
866 reverseRep = -1 if nextReverse else 1
867 label = labels[col]
868 sep = NBSP if icon else E
869 colControl = H.a(
870 f"""{label}{icon}""",
871 f"""{urlStart}&sortcol={col}&reverse={reverseRep}""",
872 )
873 headers.append((colControl, dict(cls=f"och {thisClass}")))
874 headers = (headers, {})
876 return headers
878 def disclose(self, values, colName, recCountry):
879 context = self.context
880 auth = context.auth
881 isSuperUser = self.isSuperUser
882 isCoord = auth.coordinator(countryId=recCountry)
884 disclosed = (
885 (colName not in {N.cost, REVIEWER1, REVIEWED1, REVIEWER2, REVIEWED2})
886 or isSuperUser
887 or isCoord
888 )
889 value = values[colName] if disclosed else N.undisclosed
890 return value
892 @staticmethod
893 def wrapStatus(contrib, subHead=False, kind=None):
894 aStage = G(contrib, N.astage)
895 if kind is None: 895 ↛ 908line 895 didn't jump to line 908, because the condition on line 895 was never false
896 assessed = G(contrib, N.assessed) or E
897 score = G(contrib, N.score)
898 scoreRep = E if score is None else f"""{score}% - """
899 baseLabel = assessed if subHead else G(ASSESSED_LABELS, aStage, default=E)
900 aClass = (
901 G(ASSESSED_CLASS1, assessed, default=ASSESSED_DEFAULT_CLASS)
902 if subHead
903 else G(ASSESSED_CLASS, aStage, default=ASSESSED_DEFAULT_CLASS)
904 )
905 aLabel = baseLabel if subHead else f"""{scoreRep}{baseLabel}"""
906 return (aLabel, aClass)
907 else:
908 rStage = G(contrib, N.r1Stage if kind == "1" else N.r2Stage)
909 reviewed = G(contrib, REVIEWED1 if kind == "1" else REVIEWED2) or E
910 rClass = (
911 G(REVIEW_CLASS1, reviewed, default=REVIEW_DEFAULT_CLASS)
912 if subHead
913 else G(REVIEW_CLASS, rStage, default=REVIEW_DEFAULT_CLASS)
914 )
915 rLabel = reviewed if subHead else G(REVIEW_LABELS, rStage, default=E)
916 return (rLabel, rClass)
918 @staticmethod
919 def colRep(col, n):
920 itemRep = (
921 G(COL_SINGULAR, col, default=col)
922 if n == 1
923 else G(COL_PLURAL, col, default=f"""{col}s""")
924 )
925 return f"""{n} {itemRep}"""
927 @staticmethod
928 def addGroup(groups, g):
929 return COMMA.join(groups + [g])
931 @staticmethod
932 def rmGroup(groups, g):
933 return COMMA.join(h for h in groups if h != g)
935 @staticmethod
936 def expandControls(gid, hide=False):
937 hideRep = " hide" if hide else E
938 showRep = E if hide else " hide"
939 return E.join(
940 (
941 H.iconx(N.cdown, href=E, cls=f"""dc{showRep}""", gid=gid),
942 H.iconx(N.cup, href=E, cls=f"""dc{hideRep}""", gid=gid),
943 )
944 )
946 @staticmethod
947 def expandAcontrols(group):
948 return E.join(
949 (
950 H.iconx(N.addown, href=E, cls="dca", gn=group),
951 H.iconx(N.adup, href=E, cls="dca", gn=group),
952 )
953 )
955 @staticmethod
956 def euro(amount):
957 return E if amount is None else f"""{int(round(amount)):,}"""
959 @staticmethod
960 def valTri(tri):
961 return E if tri is None else PLUS if tri else MIN
963 @staticmethod
964 def subHeadClass(col, groupSet, subHead, allHead):
965 theClass = (
966 "allhead"
967 if allHead and col == N.selected
968 else "subhead"
969 if allHead or (subHead and (col in groupSet or col in SUBHEAD_X_COLS))
970 else E
971 )
972 return f" {theClass}" if theClass else E