internationalscholarsprogram commited on
Commit
a94f84a
·
1 Parent(s): e3d540c

Fix enrollment rendering parity and header-safe Step 7 layout

Browse files
app/services/html_builder.py CHANGED
@@ -405,11 +405,47 @@ def build_handbook_html(
405
  "tier_label": tier_label,
406
  })
407
 
408
- # Stable tier ordering: Tier One (non_cosigner) → Tier Two (cosigner) → others, then alphabetical
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  def _tier_sort(u: dict) -> tuple:
 
 
 
 
 
410
  t = u.get("tier")
411
  rank = t if isinstance(t, int) else 99
412
- return (rank, (u.get("name") or "").lower(), u.get("id", 0))
413
  active_universities.sort(key=_tier_sort)
414
 
415
  # ── Normalise globals ──
 
405
  "tier_label": tier_label,
406
  })
407
 
408
+ # Explicit university display order
409
+ _UNIVERSITY_ORDER: list[str] = [
410
+ "Indiana University of Pennsylvania",
411
+ "Missouri State University",
412
+ "University of Louisville",
413
+ "University of Delaware",
414
+ "Grand Valley State University",
415
+ "Quinnipiac University",
416
+ "William Jessup University",
417
+ "Wilkes University",
418
+ "University of South Dakota",
419
+ "California Baptist University",
420
+ "Illinois State University",
421
+ "Virginia Commonwealth University",
422
+ "Rutgers University-Camden",
423
+ "University of Oklahoma",
424
+ "Saint Louis University",
425
+ "University of Alabama at Birmingham",
426
+ "Oregon State University",
427
+ "Rochester Institute of Technology",
428
+ "Lewis University",
429
+ "Texas State University",
430
+ "Drew University",
431
+ "University of Missouri- Saint Louis",
432
+ "Montana State University",
433
+ "Oklahoma City University",
434
+ "University of Dayton",
435
+ "Webster University",
436
+ "Rockhurst University",
437
+ ]
438
+ _uni_order_map = {name.lower().strip(): idx for idx, name in enumerate(_UNIVERSITY_ORDER)}
439
+
440
  def _tier_sort(u: dict) -> tuple:
441
+ name_lower = (u.get("name") or "").lower().strip()
442
+ explicit = _uni_order_map.get(name_lower)
443
+ if explicit is not None:
444
+ return (0, explicit, 0)
445
+ # Universities not in the explicit list go after, sorted by tier then alpha
446
  t = u.get("tier")
447
  rank = t if isinstance(t, int) else 99
448
+ return (1, rank, name_lower, u.get("id", 0))
449
  active_universities.sort(key=_tier_sort)
450
 
451
  # ── Normalise globals ──
app/services/normalizer.py CHANGED
@@ -363,6 +363,13 @@ def _normalize_steps(steps: list) -> list[dict]:
363
  step_title = str(s.get("title", s.get("step_title", ""))).strip()
364
  body = _normalize_text_content(str(s.get("body", s.get("description", ""))).strip())
365
 
 
 
 
 
 
 
 
366
  # Pre-format body with bold emphasis on REGULAR, PRIME, $ amounts
367
  # and convert URLs to clickable bold links
368
  from markupsafe import Markup
@@ -398,7 +405,9 @@ def _normalize_steps(steps: list) -> list[dict]:
398
  })
399
 
400
  qr = str(s.get("qr_url", s.get("qr_image", ""))).strip()
401
- if step_num == 1 and not qr:
 
 
402
  telegram_ref = ""
403
  # Check links array for Telegram URLs first
404
  for lnk in links:
@@ -412,19 +421,66 @@ def _normalize_steps(steps: list) -> list[dict]:
412
  m = re.search(r"(https?://(?:t\.me|telegram\.me)/[^\s<)]+)", body, flags=re.IGNORECASE)
413
  if m:
414
  telegram_ref = m.group(1)
 
 
 
 
415
  if telegram_ref:
 
416
  # Use branded Telegram QR from static assets (matches Word doc)
417
  # Embed as data URI so it works reliably in Playwright/Docker
418
  import base64
419
- branded_qr = Path(__file__).resolve().parent.parent / "static" / "img" / "telegram_qr.png"
420
- if branded_qr.exists():
421
- b64 = base64.b64encode(branded_qr.read_bytes()).decode()
422
- qr = f"data:image/png;base64,{b64}"
423
- else:
424
- qr = (
425
- "https://api.qrserver.com/v1/create-qr-code/?size=160x160&data="
426
- + quote_plus(telegram_ref)
427
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
  result.append({
430
  "number": step_num,
@@ -434,6 +490,8 @@ def _normalize_steps(steps: list) -> list[dict]:
434
  "links": links,
435
  "plain_links": plain_links,
436
  "qr_url": qr,
 
 
437
  })
438
  return result
439
 
 
363
  step_title = str(s.get("title", s.get("step_title", ""))).strip()
364
  body = _normalize_text_content(str(s.get("body", s.get("description", ""))).strip())
365
 
366
+ if step_num == 2:
367
+ # Fix malformed source spacing around application fee token.
368
+ body = body.replace("**$20 **that", "**$20** that")
369
+ body = body.replace("**USD 20 **that", "**$20** that")
370
+ body = re.sub(r"\bUSD\s+20\b", "$20", body, flags=re.IGNORECASE)
371
+ body = re.sub(r"\$20\s*that\b", "$20 that", body, flags=re.IGNORECASE)
372
+
373
  # Pre-format body with bold emphasis on REGULAR, PRIME, $ amounts
374
  # and convert URLs to clickable bold links
375
  from markupsafe import Markup
 
405
  })
406
 
407
  qr = str(s.get("qr_url", s.get("qr_image", ""))).strip()
408
+ telegram_url = ""
409
+ telegram_help_text = ""
410
+ if step_num == 1:
411
  telegram_ref = ""
412
  # Check links array for Telegram URLs first
413
  for lnk in links:
 
421
  m = re.search(r"(https?://(?:t\.me|telegram\.me)/[^\s<)]+)", body, flags=re.IGNORECASE)
422
  if m:
423
  telegram_ref = m.group(1)
424
+
425
+ if not telegram_ref:
426
+ telegram_ref = "https://t.me/+ivtij2xRull4yYmY5"
427
+
428
  if telegram_ref:
429
+ telegram_url = telegram_ref
430
  # Use branded Telegram QR from static assets (matches Word doc)
431
  # Embed as data URI so it works reliably in Playwright/Docker
432
  import base64
433
+ if not qr:
434
+ branded_qr = Path(__file__).resolve().parent.parent / "static" / "img" / "telegram_qr.png"
435
+ if branded_qr.exists():
436
+ b64 = base64.b64encode(branded_qr.read_bytes()).decode()
437
+ qr = f"data:image/png;base64,{b64}"
438
+ else:
439
+ qr = (
440
+ "https://api.qrserver.com/v1/create-qr-code/?size=160x160&data="
441
+ + quote_plus(telegram_ref)
442
+ )
443
+
444
+ followup_re = (
445
+ r"(This telegram group will help you interact with program administrators and "
446
+ r"other prospective students where you can ask any questions you may have about "
447
+ r"the program\.?)"
448
+ )
449
+ m_help = re.search(followup_re, body, flags=re.IGNORECASE)
450
+ if m_help:
451
+ telegram_help_text = m_help.group(1).strip()
452
+
453
+ body = re.sub(r"https?://(?:t\.me|telegram\.me)/[^\s<)]+", "", body, flags=re.IGNORECASE)
454
+ body = re.sub(followup_re, "", body, flags=re.IGNORECASE)
455
+ body = re.sub(r"\n{2,}", "\n", body).strip()
456
+ body_html = Markup(linkify_urls(emphasize_keywords(body))) if body else ""
457
+
458
+ if step_num == 2:
459
+ website_url = ""
460
+ for lnk in links:
461
+ u = str(lnk.get("url", "")).strip()
462
+ if "internationalscholarsprogram.com" in u.lower():
463
+ website_url = u
464
+ break
465
+ if website_url and "internationalscholarsprogram.com" not in body.lower():
466
+ body = re.sub(
467
+ r"\bVisit\s+and\s+submit\s+your\s+application\.",
468
+ "Visit www.internationalscholarsprogram.com and submit your application.",
469
+ body,
470
+ flags=re.IGNORECASE,
471
+ )
472
+ body = re.sub(
473
+ r"\bVisit\s{2,}and\s+submit\s+your\s+application\.",
474
+ "Visit www.internationalscholarsprogram.com and submit your application.",
475
+ body,
476
+ flags=re.IGNORECASE,
477
+ )
478
+ body_html = Markup(linkify_urls(emphasize_keywords(body))) if body else ""
479
+
480
+ links = [
481
+ l for l in links
482
+ if "internationalscholarsprogram.com" not in str(l.get("url", "")).lower()
483
+ ]
484
 
485
  result.append({
486
  "number": step_num,
 
490
  "links": links,
491
  "plain_links": plain_links,
492
  "qr_url": qr,
493
+ "telegram_url": telegram_url,
494
+ "telegram_help_text": telegram_help_text,
495
  })
496
  return result
497
 
app/static/css/print.css CHANGED
@@ -864,10 +864,10 @@ table.programs td a,
864
  }
865
 
866
  .hb-step {
867
- margin: 0 0 12pt;
868
- padding: 8pt 0 8pt 12pt;
869
- border-left: 3pt solid #199970;
870
- background: #F6FBF9;
871
  page-break-inside: avoid;
872
  break-inside: avoid;
873
  }
@@ -876,23 +876,53 @@ table.programs td a,
876
  font-size: 10.5pt;
877
  font-weight: 700;
878
  color: #199970;
879
- margin: 0 0 4pt;
880
  line-height: 1.25;
881
  page-break-after: avoid;
882
  break-after: avoid;
883
  }
884
 
885
  .hb-step-qr-wrap {
886
- margin: 6pt 0 8pt;
887
  text-align: center;
888
  }
889
 
 
 
 
 
 
 
 
 
 
 
 
 
 
890
  .hb-step-qr {
891
  display: inline-block;
892
- width: 89pt;
893
- height: 65pt;
894
- margin: 0 auto;
895
- object-fit: contain;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  }
897
 
898
  .hb-plain-url {
@@ -1563,4 +1593,4 @@ p {
1563
  image-rendering: auto;
1564
  -webkit-backface-visibility: hidden;
1565
  }
1566
- }
 
864
  }
865
 
866
  .hb-step {
867
+ margin: 0 0 14pt;
868
+ padding: 0;
869
+ border-left: none;
870
+ background: transparent;
871
  page-break-inside: avoid;
872
  break-inside: avoid;
873
  }
 
876
  font-size: 10.5pt;
877
  font-weight: 700;
878
  color: #199970;
879
+ margin: 0 0 5pt;
880
  line-height: 1.25;
881
  page-break-after: avoid;
882
  break-after: avoid;
883
  }
884
 
885
  .hb-step-qr-wrap {
886
+ margin: 4pt 0 6pt;
887
  text-align: center;
888
  }
889
 
890
+ .hb-step-link-line {
891
+ margin: 2pt 0 6pt;
892
+ text-align: left;
893
+ font-size: 10pt;
894
+ }
895
+
896
+ .hb-step-link-line a {
897
+ color: #0263A3;
898
+ text-decoration: underline;
899
+ border-bottom: none;
900
+ font-weight: 600;
901
+ }
902
+
903
  .hb-step-qr {
904
  display: inline-block;
905
+ width: 72pt;
906
+ height: 72pt;
907
+ margin: 4pt 0 6pt;
908
+ }
909
+
910
+ .hb-telegram-link {
911
+ margin: 2pt 0 6pt;
912
+ text-align: left;
913
+ }
914
+
915
+ .hb-telegram-link a {
916
+ color: #0263A3;
917
+ text-decoration: underline;
918
+ font-size: 9pt;
919
+ }
920
+
921
+ /* Step 7 often lands at a page top; force safe placement below stamped header area. */
922
+ .hb-step.hb-step-7 {
923
+ page-break-before: always;
924
+ break-before: page;
925
+ padding-top: 14pt;
926
  }
927
 
928
  .hb-plain-url {
 
1593
  image-rendering: auto;
1594
  -webkit-backface-visibility: hidden;
1595
  }
1596
+ }
app/templates/partials/blocks/enrollment_steps.html CHANGED
@@ -1,6 +1,6 @@
1
  {# Block partial: enrollment_steps — each step visually separated #}
2
  {% for step in block.data.steps %}
3
- <div class="hb-step avoid-break">
4
  {% if step.title %}
5
  <div class="hb-step-title">Step {{ step.number }}: {{ step.title | e }}</div>
6
  {% endif %}
@@ -10,23 +10,30 @@
10
  <p class="hb-paragraph">{{ step.body | e }}</p>
11
  {% endif %}
12
  {% if step.links %}
13
- <ul class="hb-bullet-list">
14
- {% for lnk in step.links %}
15
- <li><a href="{{ lnk.url | e }}" target="_blank" rel="noopener noreferrer">{{ lnk.label | e }}</a></li>
16
- {% endfor %}
17
- </ul>
18
  {% endif %}
19
  {% if step.plain_links %}
20
- <ul class="hb-bullet-list">
21
- {% for plain_url in step.plain_links %}
22
- <li><a href="{{ plain_url | e }}" target="_blank" rel="noopener noreferrer">{{ plain_url | e }}</a></li>
23
- {% endfor %}
24
- </ul>
 
 
 
 
 
25
  {% endif %}
26
  {% if step.qr_url %}
27
  <div class="hb-step-qr-wrap">
28
  <img class="hb-step-qr" src="{{ step.qr_url | e }}" alt="QR Code" />
29
  </div>
30
  {% endif %}
 
 
 
31
  </div>
32
  {% endfor %}
 
1
  {# Block partial: enrollment_steps — each step visually separated #}
2
  {% for step in block.data.steps %}
3
+ <div class="hb-step hb-step-{{ step.number }} avoid-break">
4
  {% if step.title %}
5
  <div class="hb-step-title">Step {{ step.number }}: {{ step.title | e }}</div>
6
  {% endif %}
 
10
  <p class="hb-paragraph">{{ step.body | e }}</p>
11
  {% endif %}
12
  {% if step.links %}
13
+ {% for lnk in step.links %}
14
+ <p class="hb-step-link-line"><a href="{{ lnk.url | e }}" target="_blank" rel="noopener noreferrer">{{ lnk.label | e
15
+ }}</a></p>
16
+ {% endfor %}
 
17
  {% endif %}
18
  {% if step.plain_links %}
19
+ {% for plain_url in step.plain_links %}
20
+ <p class="hb-step-link-line"><a href="{{ plain_url | e }}" target="_blank" rel="noopener noreferrer">{{ plain_url |
21
+ e
22
+ }}</a></p>
23
+ {% endfor %}
24
+ {% endif %}
25
+ {% if step.telegram_url %}
26
+ <div class="hb-telegram-link">
27
+ <a href="{{ step.telegram_url | e }}" target="_blank" rel="noopener noreferrer">{{ step.telegram_url | e }}</a>
28
+ </div>
29
  {% endif %}
30
  {% if step.qr_url %}
31
  <div class="hb-step-qr-wrap">
32
  <img class="hb-step-qr" src="{{ step.qr_url | e }}" alt="QR Code" />
33
  </div>
34
  {% endif %}
35
+ {% if step.telegram_help_text %}
36
+ <p class="hb-paragraph">{{ step.telegram_help_text | e }}</p>
37
+ {% endif %}
38
  </div>
39
  {% endfor %}