Spaces:
Paused
Paused
| """Tab 1: Room Assessment. | |
| Single room input with dimensions and assessment context. | |
| MVP Simplification: No multi-room support. | |
| """ | |
| import gradio as gr | |
| from typing import Any | |
| from ui.state import SessionState | |
| from ui.constants import CEILING_HEIGHT_PRESETS | |
| # Facility classification options | |
| FACILITY_OPTIONS = [ | |
| ("Operational", "operational"), | |
| ("Non-Operational", "non-operational"), | |
| ("Public/Childcare", "public-childcare"), | |
| ] | |
| # Construction era options | |
| CONSTRUCTION_ERA_OPTIONS = [ | |
| ("Pre-1980 (potential LBP/ACM)", "pre-1980"), | |
| ("1980-2000", "1980-2000"), | |
| ("Post-2000", "post-2000"), | |
| ] | |
| def create_tab() -> dict[str, Any]: | |
| """Create Tab 1 UI components. | |
| Returns: | |
| Dictionary of component references for event wiring. | |
| """ | |
| gr.Markdown("### Room Assessment") | |
| gr.Markdown("*Enter room details and assessment context.*") | |
| # Room identification | |
| room_name = gr.Textbox( | |
| label="Room/Area Name *", | |
| placeholder="e.g., Warehouse Bay A, Office 101", | |
| elem_id="room_name", | |
| ) | |
| # Dimensions | |
| gr.Markdown("#### Dimensions") | |
| with gr.Row(): | |
| room_length = gr.Number( | |
| label="Length (ft) *", | |
| minimum=1, | |
| value=None, | |
| elem_id="room_length", | |
| ) | |
| room_width = gr.Number( | |
| label="Width (ft) *", | |
| minimum=1, | |
| value=None, | |
| elem_id="room_width", | |
| ) | |
| with gr.Row(): | |
| room_height_preset = gr.Dropdown( | |
| label="Ceiling Height *", | |
| choices=CEILING_HEIGHT_PRESETS, | |
| elem_id="room_height_preset", | |
| info="Select preset or choose Custom", | |
| ) | |
| room_height_custom = gr.Number( | |
| label="Custom Height (ft)", | |
| minimum=1, | |
| value=None, | |
| visible=False, | |
| elem_id="room_height_custom", | |
| ) | |
| # Calculated displays | |
| with gr.Row(): | |
| floor_area = gr.Textbox( | |
| label="Floor Area (SF)", | |
| value="0", | |
| interactive=False, | |
| ) | |
| room_volume = gr.Textbox( | |
| label="Volume (CF)", | |
| value="0", | |
| interactive=False, | |
| ) | |
| # Assessment context (moved from Project tab) | |
| gr.Markdown("#### Assessment Context") | |
| facility_classification = gr.Radio( | |
| label="Facility Classification *", | |
| choices=FACILITY_OPTIONS, | |
| value="non-operational", | |
| elem_id="facility_classification", | |
| info="Affects clearance thresholds", | |
| ) | |
| construction_era = gr.Radio( | |
| label="Construction Era *", | |
| choices=CONSTRUCTION_ERA_OPTIONS, | |
| value="post-2000", | |
| elem_id="construction_era", | |
| info="Pre-1980 triggers LBP/ACM flags", | |
| ) | |
| # Validation status | |
| validation_status = gr.HTML( | |
| value="", | |
| elem_id="tab1_validation", | |
| ) | |
| validate_btn = gr.Button( | |
| "Continue to Images →", | |
| variant="primary", | |
| ) | |
| return { | |
| "room_name": room_name, | |
| "room_length": room_length, | |
| "room_width": room_width, | |
| "room_height_preset": room_height_preset, | |
| "room_height_custom": room_height_custom, | |
| "floor_area": floor_area, | |
| "room_volume": room_volume, | |
| "facility_classification": facility_classification, | |
| "construction_era": construction_era, | |
| "validation_status": validation_status, | |
| "validate_btn": validate_btn, | |
| } | |
| def on_height_preset_change(preset_value: int | None) -> dict: | |
| """Show/hide custom height input based on preset selection. | |
| Args: | |
| preset_value: The selected preset value, or None for "Custom". | |
| Returns: | |
| Gradio update dict for custom height visibility. | |
| """ | |
| return gr.update(visible=(preset_value is None)) | |
| def update_calculated_values( | |
| length: float | None, | |
| width: float | None, | |
| height_preset: int | None, | |
| height_custom: float | None, | |
| ) -> tuple[str, str]: | |
| """Calculate and return floor area and volume. | |
| Returns: | |
| Tuple of (floor_area_str, volume_str). | |
| """ | |
| # Get effective values | |
| length_val = float(length) if length and length > 0 else 0 | |
| width_val = float(width) if width and width > 0 else 0 | |
| if height_preset is not None: | |
| height_val = float(height_preset) | |
| elif height_custom is not None and height_custom > 0: | |
| height_val = float(height_custom) | |
| else: | |
| height_val = 0 | |
| # Calculate | |
| area = length_val * width_val | |
| volume = area * height_val | |
| return f"{area:,.0f}", f"{volume:,.0f}" | |
| def save_room_to_session( | |
| session: SessionState, | |
| name: str, | |
| length: float | None, | |
| width: float | None, | |
| height_preset: int | None, | |
| height_custom: float | None, | |
| facility_classification: str, | |
| construction_era: str, | |
| ) -> SessionState: | |
| """Save room data to session (called on field changes). | |
| Returns: | |
| Updated session. | |
| """ | |
| # Determine actual ceiling height | |
| if height_preset is not None: | |
| height = float(height_preset) | |
| elif height_custom is not None and height_custom > 0: | |
| height = float(height_custom) | |
| else: | |
| height = 0 | |
| # Update session room data | |
| session.room.name = name.strip() if name else "" | |
| session.room.length_ft = float(length) if length and length > 0 else 0 | |
| session.room.width_ft = float(width) if width and width > 0 else 0 | |
| session.room.ceiling_height_ft = height | |
| session.room.facility_classification = facility_classification | |
| session.room.construction_era = construction_era | |
| session.update_timestamp() | |
| return session | |
| def validate_and_continue(session: SessionState) -> tuple[SessionState, str, int]: | |
| """Validate Tab 1 and proceed to Tab 2. | |
| Returns: | |
| Tuple of (session, validation_html, next_tab_index). | |
| """ | |
| is_valid, errors = session.validate_tab1() | |
| if is_valid: | |
| session.tab1_complete = True | |
| session.update_timestamp() | |
| html = """ | |
| <div style="background: #e8f5e9; border: 1px solid #66bb6a; border-radius: 4px; padding: 10px;"> | |
| <span style="color: #2e7d32;">✓ Room details complete. Proceeding to Images tab...</span> | |
| </div> | |
| """ | |
| return session, html, gr.update(selected=1) # Go to tab index 1 (Images) | |
| else: | |
| session.tab1_complete = False | |
| error_items = "".join(f"<li>{e}</li>" for e in errors) | |
| html = f""" | |
| <div style="background: #ffebee; border: 1px solid #ef5350; border-radius: 4px; padding: 10px;"> | |
| <strong style="color: #c62828;">Please fix the following:</strong> | |
| <ul style="margin: 5px 0 0 0; padding-left: 20px; color: #c62828;"> | |
| {error_items} | |
| </ul> | |
| </div> | |
| """ | |
| return session, html, gr.update(selected=0) # Stay on current tab | |
| def load_from_session(session: SessionState) -> tuple[str, float | None, float | None, int | None, float | None, str, str, str, str]: | |
| """Load room data from session. | |
| Returns: | |
| Tuple of (name, length, width, height_preset, height_custom, | |
| floor_area, volume, facility_classification, construction_era). | |
| """ | |
| r = session.room | |
| # Determine if height matches a preset or is custom | |
| height_preset = None | |
| height_custom = None | |
| # Check if ceiling height matches a preset value | |
| preset_values = [p[1] for p in CEILING_HEIGHT_PRESETS if p[1] is not None] | |
| if r.ceiling_height_ft in preset_values: | |
| height_preset = int(r.ceiling_height_ft) | |
| elif r.ceiling_height_ft > 0: | |
| height_custom = r.ceiling_height_ft | |
| # Calculate stats | |
| area = r.length_ft * r.width_ft | |
| volume = area * r.ceiling_height_ft | |
| return ( | |
| r.name, | |
| r.length_ft if r.length_ft > 0 else None, | |
| r.width_ft if r.width_ft > 0 else None, | |
| height_preset, | |
| height_custom, | |
| f"{area:,.0f}", | |
| f"{volume:,.0f}", | |
| r.facility_classification, | |
| r.construction_era, | |
| ) | |