Eishaan commited on
Commit
cb4d56e
·
1 Parent(s): ba4c05d

Polish submission workflow and board preview

Browse files
Files changed (3) hide show
  1. app.py +47 -7
  2. assets/style.css +218 -3
  3. src/board_export.py +18 -7
app.py CHANGED
@@ -50,7 +50,23 @@ def read_asset(name: str) -> str:
50
 
51
  def image_preview_html(path: str | None) -> str:
52
  if not path:
53
- return "<div class='board-preview-empty'>Generate an analysis to create the presentation board preview.</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  try:
55
  source = validate_upload(
56
  path,
@@ -104,6 +120,15 @@ MAP_HTML = """
104
  </div>
105
  """
106
 
 
 
 
 
 
 
 
 
 
107
  SAMPLE_MAP_STATE = json.dumps(
108
  {
109
  "mode": "polygon",
@@ -714,6 +739,13 @@ def build_app() -> gr.Blocks:
714
  <span>Climate, terrain, soil, OSM, and AI text are labelled by source, confidence, and verification need.</span>
715
  </div>
716
  </div>
 
 
 
 
 
 
 
717
  </div>
718
  """
719
  )
@@ -770,6 +802,7 @@ def build_app() -> gr.Blocks:
770
  </div>
771
  """
772
  )
 
773
  gr.HTML(MAP_HTML)
774
  map_state = gr.Textbox(label="Map state", elem_id="map_state", lines=4)
775
  gr.HTML(
@@ -878,9 +911,7 @@ def build_app() -> gr.Blocks:
878
  map_state,
879
  ],
880
  )
881
- generate_btn.click(
882
- fn=generate_site_analysis,
883
- inputs=[
884
  project_name,
885
  site_name,
886
  project_type,
@@ -895,8 +926,8 @@ def build_app() -> gr.Blocks:
895
  geojson_file,
896
  kml_file,
897
  pdf_file,
898
- ],
899
- outputs=[
900
  board_preview,
901
  board_png_file,
902
  board_pdf_file,
@@ -911,7 +942,16 @@ def build_app() -> gr.Blocks:
911
  evidence_table,
912
  warnings_output,
913
  assistant_output,
914
- ],
 
 
 
 
 
 
 
 
 
915
  )
916
  if "js" not in block_parameters:
917
  demo.load(fn=None, js=map_js_callback)
 
50
 
51
  def image_preview_html(path: str | None) -> str:
52
  if not path:
53
+ return """
54
+ <div class="board-preview-empty board-preview-waiting">
55
+ <div class="empty-board-grid">
56
+ <div class="empty-board-title">
57
+ <span>Waiting for selected land</span>
58
+ <b>Presentation board will appear here</b>
59
+ <small>Draw a site boundary, drop a pin radius, or upload CAD/KML/GeoJSON. Optional project fields can stay blank.</small>
60
+ </div>
61
+ <div class="empty-board-card"><b>01</b><span>Boundary metrics</span></div>
62
+ <div class="empty-board-card"><b>02</b><span>Climate + sun/wind</span></div>
63
+ <div class="empty-board-card"><b>03</b><span>OSM context</span></div>
64
+ <div class="empty-board-card"><b>04</b><span>Terrain + soil caution</span></div>
65
+ <div class="empty-board-card"><b>05</b><span>Site-visit checklist</span></div>
66
+ <div class="empty-board-card"><b>06</b><span>Evidence table</span></div>
67
+ </div>
68
+ </div>
69
+ """
70
  try:
71
  source = validate_upload(
72
  path,
 
120
  </div>
121
  """
122
 
123
+ DELIVERABLES_HTML = """
124
+ <div class="deliverables-strip">
125
+ <div><b>Board PNG/PDF</b><span>sheet-style artifact</span></div>
126
+ <div><b>6 diagrams</b><span>climate, context, edge, matrix</span></div>
127
+ <div><b>Evidence</b><span>source / limit / verification</span></div>
128
+ <div><b>Report</b><span>editable workbook text</span></div>
129
+ </div>
130
+ """
131
+
132
  SAMPLE_MAP_STATE = json.dumps(
133
  {
134
  "mode": "polygon",
 
739
  <span>Climate, terrain, soil, OSM, and AI text are labelled by source, confidence, and verification need.</span>
740
  </div>
741
  </div>
742
+ <div class="studio-proof-row">
743
+ <div><b>For students</b><span>faster studio sheets</span></div>
744
+ <div><b>For reviewers</b><span>sources and limits visible</span></div>
745
+ <div><b>For safety</b><span>no final foundation or legal claims</span></div>
746
+ <div><b>For demo</b><span>works from polygon only</span></div>
747
+ </div>
748
+ """ + DELIVERABLES_HTML + """
749
  </div>
750
  """
751
  )
 
802
  </div>
803
  """
804
  )
805
+ map_generate_btn = gr.Button("Generate from selected site", variant="primary", elem_classes=["map-generate-button"])
806
  gr.HTML(MAP_HTML)
807
  map_state = gr.Textbox(label="Map state", elem_id="map_state", lines=4)
808
  gr.HTML(
 
911
  map_state,
912
  ],
913
  )
914
+ analysis_inputs = [
 
 
915
  project_name,
916
  site_name,
917
  project_type,
 
926
  geojson_file,
927
  kml_file,
928
  pdf_file,
929
+ ]
930
+ analysis_outputs = [
931
  board_preview,
932
  board_png_file,
933
  board_pdf_file,
 
942
  evidence_table,
943
  warnings_output,
944
  assistant_output,
945
+ ]
946
+ generate_btn.click(
947
+ fn=generate_site_analysis,
948
+ inputs=analysis_inputs,
949
+ outputs=analysis_outputs,
950
+ )
951
+ map_generate_btn.click(
952
+ fn=generate_site_analysis,
953
+ inputs=analysis_inputs,
954
+ outputs=analysis_outputs,
955
  )
956
  if "js" not in block_parameters:
957
  demo.load(fn=None, js=map_js_callback)
assets/style.css CHANGED
@@ -341,9 +341,42 @@ body {
341
  line-height: 1.42;
342
  }
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  .studio-workbench {
345
  display: grid !important;
346
- grid-template-columns: minmax(260px, 0.82fr) minmax(560px, 1.7fr) minmax(280px, 0.9fr);
347
  gap: 16px !important;
348
  align-items: start !important;
349
  margin-top: 16px;
@@ -517,6 +550,47 @@ body {
517
  line-height: 1.4;
518
  }
519
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  .review-header {
521
  margin: 22px 0 12px;
522
  border: 1px solid var(--rule);
@@ -846,6 +920,7 @@ body {
846
  }
847
 
848
  .generate-button button,
 
849
  button.primary {
850
  min-height: 54px !important;
851
  background: linear-gradient(90deg, var(--survey-green), #0b5f57) !important;
@@ -860,11 +935,18 @@ button.primary {
860
  }
861
 
862
  .generate-button button:hover,
 
863
  button.primary:hover {
864
  background: linear-gradient(90deg, var(--cad-blue), var(--survey-green-dark)) !important;
865
  transform: translate(-1px, -1px);
866
  }
867
 
 
 
 
 
 
 
868
  .tabs {
869
  margin-top: 20px !important;
870
  }
@@ -932,6 +1014,96 @@ button.primary:hover {
932
  background-size: 32px 32px, 32px 32px, auto;
933
  }
934
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
935
  .board-preview-frame img {
936
  display: block;
937
  width: 100%;
@@ -1256,16 +1428,28 @@ footer {
1256
  }
1257
 
1258
  .studio-briefing,
 
1259
  .studio-workbench {
1260
  grid-template-columns: 1fr !important;
1261
  }
1262
 
1263
- .studio-briefing div {
 
 
 
 
 
 
 
 
 
 
1264
  border-right: 0;
1265
  border-bottom: 1px solid var(--line);
1266
  }
1267
 
1268
- .studio-briefing div:last-child {
 
1269
  border-bottom: 0;
1270
  }
1271
 
@@ -1343,6 +1527,37 @@ footer {
1343
  max-width: 128px;
1344
  }
1345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1346
  .control-panel {
1347
  margin-top: 16px !important;
1348
  }
 
341
  line-height: 1.42;
342
  }
343
 
344
+ .studio-proof-row {
345
+ display: grid;
346
+ grid-template-columns: repeat(4, minmax(0, 1fr));
347
+ border-top: 1px solid var(--rule);
348
+ background: #f3f8f6;
349
+ }
350
+
351
+ .studio-proof-row div {
352
+ min-height: 68px;
353
+ padding: 13px 18px;
354
+ border-right: 1px solid var(--line);
355
+ }
356
+
357
+ .studio-proof-row div:last-child {
358
+ border-right: 0;
359
+ }
360
+
361
+ .studio-proof-row b {
362
+ display: block;
363
+ color: var(--survey-green-dark);
364
+ font-family: var(--font-display);
365
+ font-size: 13px;
366
+ font-weight: 900;
367
+ text-transform: uppercase;
368
+ }
369
+
370
+ .studio-proof-row span {
371
+ display: block;
372
+ margin-top: 4px;
373
+ color: var(--ink-soft) !important;
374
+ font-size: 13px;
375
+ }
376
+
377
  .studio-workbench {
378
  display: grid !important;
379
+ grid-template-columns: minmax(230px, 0.68fr) minmax(660px, 1.9fr) minmax(270px, 0.78fr);
380
  gap: 16px !important;
381
  align-items: start !important;
382
  margin-top: 16px;
 
550
  line-height: 1.4;
551
  }
552
 
553
+ .deliverables-strip {
554
+ display: grid;
555
+ grid-template-columns: repeat(4, minmax(0, 1fr));
556
+ gap: 0;
557
+ margin: 12px 0;
558
+ border: 1px solid var(--rule);
559
+ border-radius: var(--radius);
560
+ overflow: hidden;
561
+ background: #ffffff;
562
+ box-shadow: var(--shadow-flat);
563
+ }
564
+
565
+ .deliverables-strip div {
566
+ min-height: 72px;
567
+ padding: 13px 14px;
568
+ border-right: 1px solid var(--line);
569
+ background:
570
+ linear-gradient(180deg, rgba(15, 118, 110, 0.07), transparent 65%),
571
+ #ffffff;
572
+ }
573
+
574
+ .deliverables-strip div:last-child {
575
+ border-right: 0;
576
+ }
577
+
578
+ .deliverables-strip b {
579
+ display: block;
580
+ color: var(--ink);
581
+ font-family: var(--font-display);
582
+ font-size: 15px;
583
+ font-weight: 900;
584
+ }
585
+
586
+ .deliverables-strip span {
587
+ display: block;
588
+ margin-top: 5px;
589
+ color: var(--ink-soft) !important;
590
+ font-size: 12px;
591
+ line-height: 1.35;
592
+ }
593
+
594
  .review-header {
595
  margin: 22px 0 12px;
596
  border: 1px solid var(--rule);
 
920
  }
921
 
922
  .generate-button button,
923
+ .map-generate-button button,
924
  button.primary {
925
  min-height: 54px !important;
926
  background: linear-gradient(90deg, var(--survey-green), #0b5f57) !important;
 
935
  }
936
 
937
  .generate-button button:hover,
938
+ .map-generate-button button:hover,
939
  button.primary:hover {
940
  background: linear-gradient(90deg, var(--cad-blue), var(--survey-green-dark)) !important;
941
  transform: translate(-1px, -1px);
942
  }
943
 
944
+ .map-generate-button button {
945
+ width: 100% !important;
946
+ min-height: 58px !important;
947
+ font-size: 17px !important;
948
+ }
949
+
950
  .tabs {
951
  margin-top: 20px !important;
952
  }
 
1014
  background-size: 32px 32px, 32px 32px, auto;
1015
  }
1016
 
1017
+ .board-preview-waiting {
1018
+ min-height: 560px;
1019
+ padding: 28px;
1020
+ place-items: stretch;
1021
+ }
1022
+
1023
+ .empty-board-grid {
1024
+ display: grid;
1025
+ height: 100%;
1026
+ min-height: 500px;
1027
+ grid-template-columns: 1.55fr repeat(2, minmax(0, 1fr));
1028
+ grid-auto-rows: minmax(130px, auto);
1029
+ gap: 14px;
1030
+ }
1031
+
1032
+ .empty-board-title {
1033
+ grid-row: span 2;
1034
+ display: flex;
1035
+ flex-direction: column;
1036
+ justify-content: flex-end;
1037
+ border: 1px solid var(--rule);
1038
+ border-radius: var(--radius);
1039
+ padding: 24px;
1040
+ background:
1041
+ linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px),
1042
+ linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px),
1043
+ #101716;
1044
+ background-size: 32px 32px, 32px 32px, auto;
1045
+ color: #ffffff;
1046
+ }
1047
+
1048
+ .empty-board-title span {
1049
+ color: #8ce6dc !important;
1050
+ font-family: var(--font-mono);
1051
+ font-size: 12px;
1052
+ font-weight: 850;
1053
+ letter-spacing: 0.08em;
1054
+ text-transform: uppercase;
1055
+ }
1056
+
1057
+ .empty-board-title b {
1058
+ display: block;
1059
+ max-width: 540px;
1060
+ margin-top: 12px;
1061
+ color: #ffffff;
1062
+ font-family: var(--font-display);
1063
+ font-size: clamp(31px, 3vw, 54px);
1064
+ font-weight: 900;
1065
+ line-height: 0.98;
1066
+ }
1067
+
1068
+ .empty-board-title small {
1069
+ display: block;
1070
+ max-width: 560px;
1071
+ margin-top: 14px;
1072
+ color: #d5e0dd !important;
1073
+ font-size: 15px;
1074
+ line-height: 1.45;
1075
+ }
1076
+
1077
+ .empty-board-card {
1078
+ border: 1px solid var(--line-strong);
1079
+ border-radius: var(--radius);
1080
+ padding: 18px;
1081
+ background: #ffffff;
1082
+ color: var(--ink);
1083
+ box-shadow: 4px 4px 0 rgba(17, 22, 20, 0.08);
1084
+ }
1085
+
1086
+ .empty-board-card b {
1087
+ display: grid;
1088
+ width: 36px;
1089
+ height: 36px;
1090
+ place-items: center;
1091
+ margin-bottom: 20px;
1092
+ background: var(--ink);
1093
+ color: #ffffff;
1094
+ font-family: var(--font-mono);
1095
+ font-size: 12px;
1096
+ }
1097
+
1098
+ .empty-board-card span {
1099
+ display: block;
1100
+ color: var(--ink);
1101
+ font-family: var(--font-display);
1102
+ font-size: 19px;
1103
+ font-weight: 900;
1104
+ line-height: 1.12;
1105
+ }
1106
+
1107
  .board-preview-frame img {
1108
  display: block;
1109
  width: 100%;
 
1428
  }
1429
 
1430
  .studio-briefing,
1431
+ .studio-proof-row,
1432
  .studio-workbench {
1433
  grid-template-columns: 1fr !important;
1434
  }
1435
 
1436
+ .studio-shell > .deliverables-strip,
1437
+ .studio-proof-row {
1438
+ display: none;
1439
+ }
1440
+
1441
+ .studio-briefing div:nth-child(n + 2) {
1442
+ display: none;
1443
+ }
1444
+
1445
+ .studio-briefing div,
1446
+ .studio-proof-row div {
1447
  border-right: 0;
1448
  border-bottom: 1px solid var(--line);
1449
  }
1450
 
1451
+ .studio-briefing div:last-child,
1452
+ .studio-proof-row div:last-child {
1453
  border-bottom: 0;
1454
  }
1455
 
 
1527
  max-width: 128px;
1528
  }
1529
 
1530
+ .deliverables-strip {
1531
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1532
+ }
1533
+
1534
+ .deliverables-strip div:nth-child(2n) {
1535
+ border-right: 0;
1536
+ }
1537
+
1538
+ .deliverables-strip div {
1539
+ border-bottom: 1px solid var(--line);
1540
+ }
1541
+
1542
+ .deliverables-strip div:nth-last-child(-n + 2) {
1543
+ border-bottom: 0;
1544
+ }
1545
+
1546
+ .empty-board-grid {
1547
+ grid-template-columns: 1fr;
1548
+ min-height: auto;
1549
+ }
1550
+
1551
+ .empty-board-title {
1552
+ grid-row: auto;
1553
+ min-height: 260px;
1554
+ }
1555
+
1556
+ .board-preview-waiting {
1557
+ min-height: auto;
1558
+ padding: 14px;
1559
+ }
1560
+
1561
  .control-panel {
1562
  margin-top: 16px !important;
1563
  }
src/board_export.py CHANGED
@@ -363,13 +363,24 @@ def _context_caption(osm_context: dict[str, Any], selection: SiteSelection) -> s
363
 
364
 
365
  def _sun_caption(sun_summary: dict[str, str]) -> str:
366
- pieces = [
367
- sun_summary.get("orientation_note", ""),
368
- sun_summary.get("east_west_note", ""),
369
- sun_summary.get("wind_note", ""),
370
- "No surrounding-building shadow simulation.",
371
- ]
372
- return " ".join(piece for piece in pieces if piece)
 
 
 
 
 
 
 
 
 
 
 
373
 
374
 
375
  def _design_cues(
 
363
 
364
 
365
  def _sun_caption(sun_summary: dict[str, str]) -> str:
366
+ orientation = sun_summary.get("orientation_note", "")
367
+ if "southern" in orientation.lower():
368
+ orientation = "Sun cue: generally southern sky at this latitude."
369
+ elif orientation:
370
+ orientation = "Sun cue: use latitude-based solar orientation."
371
+ else:
372
+ orientation = "Sun cue unavailable; verify with sun-path study."
373
+ wind = sun_summary.get("wind_note", "")
374
+ if wind:
375
+ wind = wind.replace("Available climate data suggests dominant wind around", "Wind cue:")
376
+ wind = wind.replace("Treat this as regional context and verify on site.", "Verify on site.")
377
+ wind = _clip(wind, 62)
378
+ else:
379
+ wind = "Wind: regional/modelled cue only; verify comfort and ventilation on site."
380
+ return (
381
+ f"{orientation} West/east edges need glare and heat checks. "
382
+ f"{wind} No shadow sim."
383
+ )
384
 
385
 
386
  def _design_cues(