Hide keyboard shortcuts

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. 

2 

3* Country selection 

4* Grouping by categories 

5* Statistics 

6""" 

7 

8import json 

9from flask import request, make_response 

10 

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 

14 

15 

16CT = C.tables 

17CW = C.web 

18 

19URLS = CW.urls 

20PAGE = URLS[N.info][N.url] 

21PAGEX = f"""{PAGE}.tsv""" 

22 

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) 

32 

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) 

42 

43REVIEWER1 = "reviewer1" 

44REVIEWER2 = "reviewer2" 

45REVIEWED1 = "reviewed1" 

46REVIEWED2 = "reviewed2" 

47R1RANK = "r1Rank" 

48R2RANK = "r2Rank" 

49 

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) 

63 

64GROUP_COLS = f""" 

65 country 

66 year 

67 type 

68 assessed 

69 {REVIEWER1} 

70 {REVIEWER2} 

71 {REVIEWED1} 

72 {REVIEWED2} 

73 selected 

74""".strip().split() 

75 

76SUBHEAD_X_COLS = set( 

77 """ 

78 cost 

79 title 

80""".strip().split() 

81) 

82 

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)} 

101 

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} 

124 

125 

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)} 

143 

144ALL = """All countries""" 

145 

146 

147class Overview: 

148 def __init__(self, context): 

149 self.context = context 

150 

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 

157 

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 

164 

165 self.userGroup = auth.groupRep() 

166 self.myCountry = auth.countryRep() 

167 

168 userCountryId = G(user, N.country) 

169 chosenCountry = None 

170 chosenCountryIso = None 

171 chosenCountryId = None 

172 

173 countryId = G(db.countryInv, country) if country else userCountryId 

174 

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) 

180 

181 self.chosenCountry = chosenCountry 

182 self.chosenCountryId = chosenCountryId 

183 self.chosenCountryIso = chosenCountryIso 

184 

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 

194 

195 users = db.user 

196 

197 contribs = {} 

198 for record in db.bulkContribWorkflow(chosenCountryId, bulk): 

199 title = G(record, N.title) 

200 contribId = G(record, N._id) 

201 

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 

212 

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) 

217 

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 

279 

280 self.contribs = contribs 

281 

282 def roTri(self, tri): 

283 return self.bool3Obj.toDisplay(tri, markup=False) 

284 

285 def wrap(self, asTsv=False): 

286 context = self.context 

287 db = context.db 

288 auth = context.auth 

289 countryType = self.countryType 

290 

291 isSuperUser = auth.superuser() 

292 self.isSuperUser = isSuperUser 

293 self.isCoord = auth.coordinator() 

294 

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) 

301 

302 accessRep = auth.credentials()[1] 

303 

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 ) 

312 

313 self.getCountry(country) 

314 self.getContribs(bulk) 

315 

316 chosenCountry = self.chosenCountry 

317 chosenCountryId = self.chosenCountryId 

318 chosenCountryIso = self.chosenCountryIso 

319 

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] 

324 

325 cols = [c[0] for c in colSpecs] 

326 colSet = {c[0] for c in colSpecs} 

327 

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] 

331 

332 groupsChosen = [] if not groups else groups.split(COMMA) 

333 groupSet = set(groupsChosen) 

334 groupStr = ("""-by-""" if groupSet else E) + MIN.join(sorted(groupSet)) 

335 

336 sortCol = sortDefault if rawSortCol not in colSet else rawSortCol 

337 reverse = False if rawReverse not in {MINONE, ONE} else rawReverse == MINONE 

338 

339 self.cols = cols 

340 self.labels = labels 

341 self.bulk = bulk 

342 self.groupCols = groupCols 

343 self.sortCol = sortCol 

344 self.reverse = reverse 

345 

346 material = [] 

347 if not asTsv: 

348 material.append(H.h(3, """Country selection""")) 

349 

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) 

369 

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)) 

384 

385 groupsAvailable = sorted(allGroupSet - set(groupsChosen)) 

386 groupOrder = groupsChosen + [g for g in cols if g not in groupSet] 

387 

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}""" 

422 

423 headerLine = self.ourCountryHeaders( 

424 country, 

425 groups, 

426 asTsv, 

427 groupOrder=groupOrder, 

428 ) 

429 

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() 

435 

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 ) 

480 

481 (thisMaterial, groupRel) = self.groupList( 

482 groupsChosen, 

483 chosenCountry, 

484 chosenCountry, 

485 asTsv, 

486 ) 

487 

488 if asTsv: 

489 material.append(thisMaterial) 

490 else: 

491 material.append(H.table([headerLine], thisMaterial, cls="cc")) 

492 material.append(groupRel) 

493 

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) 

514 

515 return data 

516 

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 

529 

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 ) 

560 

561 preGroups = groups[0:-1] 

562 lastGroup = groups[-1] 

563 

564 groupLen = len(groups) 

565 groupSet = set(groups) 

566 groupOrder = groups + [g for g in cols if g not in groupSet] 

567 

568 groupedList = {} 

569 

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) 

576 

577 material = [] 

578 maxGroupId = 1 

579 groupRel = {} 

580 

581 def groupMaterial(gList, depth, groupValues, parentGroupId): 

582 groupSet = set(groupValues.keys()) 

583 

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)) 

589 

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) 

666 

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 ) 

672 

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 

691 

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 ) 

721 

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) 

792 

793 def contribKey(self, col, individual=False): 

794 types = self.types 

795 

796 colType = types[col] 

797 

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 

815 

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 

828 

829 return makeKeyInd if individual else makeKey 

830 

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 

837 

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 

840 

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 

849 

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, {}) 

875 

876 return headers 

877 

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) 

883 

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 

891 

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) 

917 

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}""" 

926 

927 @staticmethod 

928 def addGroup(groups, g): 

929 return COMMA.join(groups + [g]) 

930 

931 @staticmethod 

932 def rmGroup(groups, g): 

933 return COMMA.join(h for h in groups if h != g) 

934 

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 ) 

945 

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 ) 

954 

955 @staticmethod 

956 def euro(amount): 

957 return E if amount is None else f"""{int(round(amount)):,}""" 

958 

959 @staticmethod 

960 def valTri(tri): 

961 return E if tri is None else PLUS if tri else MIN 

962 

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