Spaces:
Sleeping
Sleeping
File size: 10,854 Bytes
b3d58ff | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | # To run this app:
# 1. Install Gradio: !pip install gradio
# 2. Run from your terminal: python lighthouse.py
import gradio as gr
import json
from datetime import date
# ===================================================================================
# PROJECT CODE STARTS HERE
# Students will modify the code below this line.
# ===================================================================================
# ### WEEK 1: The Blueprint - Modeling a Lighthouse ###
# Focus: Abstraction, Automation, Analysis
# -----------------------------------------------------------------------------------
# TODO: Implement the Lighthouse data model.
# - Use a class to represent a lighthouse.
# - Add validation logic inside its __init__ method.
class Lighthouse:
"""Represents a single lighthouse with validation."""
def __init__(self, name: str, location: str, is_operational: bool, last_inspected_date: str):
# Validate name: must be a non-empty string
if not isinstance(name, str) or not name.strip():
raise ValueError("Name must be a non-empty string.")
# Validate location: must be a non-empty string
if not isinstance(location, str) or not location.strip():
raise ValueError("Location must be a non-empty string.")
# Validate is_operational: must be a boolean
if not isinstance(is_operational, bool):
raise TypeError("Is Operational must be a boolean (True/False).")
# Validate date: must be in 'YYYY-MM-DD' format
try:
parsed_date = date.fromisoformat(last_inspected_date)
except (ValueError, TypeError):
raise ValueError("Last Inspected Date must be in YYYY-MM-DD format.")
self.name = name.strip()
self.location = location.strip()
self.is_operational = is_operational
self.last_inspected_date = parsed_date
def to_dict(self):
"""Converts the object to a dictionary for JSON serialization."""
return {
"name": self.name,
"location": self.location,
"is_operational": self.is_operational,
"last_inspected_date": self.last_inspected_date.isoformat()
}
def __repr__(self):
"""Provides a developer-friendly string representation of the object."""
status = "Operational" if self.is_operational else "Non-Operational"
return f"β
{self.name} ({self.location}) - Status: {status}, Last Inspected: {self.last_inspected_date.isoformat()}"
# ### WEEK 5: Scaling Up - The Lighthouse Authority ###
# Focus: Abstraction, Automation, Analysis
# -----------------------------------------------------------------------------------
# TODO: Refactor all functions into this LighthouseRegistry class.
class LighthouseRegistry:
"""Manages the collection of all lighthouses."""
def __init__(self, filepath="lighthouses.json"):
self.lighthouses = []
self.filepath = filepath
# ### WEEK 2: First Steps - The Lighthouse Logbook ###
# TODO: Implement the add_lighthouse method.
def add_lighthouse(self, name: str, location: str, is_operational: bool, last_inspected_date: str):
"""Creates and adds a new Lighthouse to the registry after validation."""
try:
new_lighthouse = Lighthouse(name, location, is_operational, last_inspected_date)
self.lighthouses.append(new_lighthouse)
return f"Success: Added '{name}' to the registry."
except (ValueError, TypeError) as e:
return f"β Error: Could not add lighthouse. {e}"
# ### WEEK 3: Making Sense of the Data - Fleet Status Reports ###
# TODO: Implement the generate_status_report method for ANALYSIS.
def generate_status_report(self):
"""Analyzes the current list of lighthouses and generates a summary report."""
if not self.lighthouses:
return "Cannot generate report: No lighthouses in the registry."
total = len(self.lighthouses)
operational_count = sum(1 for lh in self.lighthouses if lh.is_operational)
non_operational_count = total - operational_count
most_recent_lighthouse = max(self.lighthouses, key=lambda lh: lh.last_inspected_date)
report = (
"π ===== Lighthouse Fleet Status Report ===== π\n"
f"Total Lighthouses: {total}\n"
f" - Operational: {operational_count}\n"
f" - Non-Operational: {non_operational_count}\n\n"
f"Most Recently Inspected:\n"
f" - Name: {most_recent_lighthouse.name}\n"
f" - Date: {most_recent_lighthouse.last_inspected_date.isoformat()}\n"
"=========================================="
)
return report
def list_lighthouses(self):
"""Returns a formatted string listing all lighthouses in the registry."""
if not self.lighthouses:
return "No lighthouses in the registry."
header = "--- List of All Lighthouses ---"
# Use the __repr__ from the Lighthouse class for a clean format
report_lines = [repr(lh) for lh in self.lighthouses]
return f"{header}\n" + "\n".join(report_lines)
# ### WEEK 4: Making It Real - The Official Record ###
# TODO: Implement save_to_file and load_from_file to AUTOMATE persistence.
def save_to_file(self):
"""Saves the current list of lighthouses to the JSON file."""
try:
# Convert each Lighthouse object to its dictionary representation
data_to_save = [lh.to_dict() for lh in self.lighthouses]
with open(self.filepath, 'w') as f:
json.dump(data_to_save, f, indent=4)
return f"πΎ Success: Saved {len(self.lighthouses)} lighthouses to {self.filepath}."
except (IOError, TypeError) as e:
return f"β Error: Could not save to file. {e}"
def load_from_file(self):
"""Loads lighthouses from the JSON file, recreating the objects."""
try:
with open(self.filepath, 'r') as f:
data = json.load(f)
self.lighthouses = [] # Clear the current list before loading
for item in data:
# The Lighthouse constructor handles validation of the loaded data
lh = Lighthouse(**item)
self.lighthouses.append(lh)
return f"π Success: Loaded {len(self.lighthouses)} lighthouses from {self.filepath}."
except FileNotFoundError:
return f"βΉοΈ Info: No save file found at '{self.filepath}'. Starting with an empty registry."
except (json.JSONDecodeError, ValueError, TypeError) as e:
# Catches corrupted JSON, or data that fails Lighthouse validation
return f"β Error: Could not load from file. File may be corrupt or malformed. {e}"
# ===================================================================================
# GRADIO UI CODE
# Students should not need to modify below this line initially,
# but can extend it in Week 5.
# ===================================================================================
def create_ui(registry: LighthouseRegistry):
"""Creates the Gradio web interface."""
# Helper functions to interact with the registry and update the UI
def add_and_update(name, location, is_op, date_str, current_log):
result = registry.add_lighthouse(name, location, is_op, date_str)
new_log = f"{current_log}\n> {result}"
return new_log
def report_and_update(current_log):
result = registry.generate_status_report()
new_log = f"{current_log}\n\n{result}\n"
return new_log
def list_and_update(current_log):
result = registry.list_lighthouses()
new_log = f"{current_log}\n\n{result}\n"
return new_log
def save_and_update(current_log):
result = registry.save_to_file()
new_log = f"{current_log}\n> {result}"
return new_log
def load_and_update(current_log):
result = registry.load_from_file()
new_log = f"{current_log}\n> {result}"
# After loading, also list the lighthouses to show what was loaded
list_result = registry.list_lighthouses()
return f"{new_log}\n\n{list_result}\n"
# Define the Gradio interface layout
with gr.Blocks(theme=gr.themes.Monochrome(), title="Lighthouse Lookout") as app:
gr.Markdown("# π’ Lighthouse Lookout")
gr.Markdown("A tool for monitoring and managing a fleet of lighthouses.")
log_textbox = gr.Textbox(
label="Log & Reports",
value="Welcome! Your command results will appear here.",
lines=15,
interactive=False,
elem_classes="terminal" # Just for potential styling
)
with gr.Row():
report_btn = gr.Button("π Generate Status Report")
list_btn = gr.Button("π List All Lighthouses")
with gr.Accordion("β Add a New Lighthouse", open=False):
with gr.Row():
name_input = gr.Textbox(label="Name", placeholder="e.g., Trinity Lighthouse")
location_input = gr.Textbox(label="Location", placeholder="e.g., 50.18, -0.16")
with gr.Row():
is_operational_input = gr.Checkbox(label="Is Operational?", value=True)
# Using a string for the date to align with the Lighthouse class constructor
date_input = gr.Textbox(label="Last Inspected Date", placeholder="YYYY-MM-DD")
add_btn = gr.Button("Add Lighthouse")
with gr.Row():
save_btn = gr.Button("πΎ Save Registry")
load_btn = gr.Button("π Load Registry")
# Connect UI components to the helper functions
add_btn.click(
fn=add_and_update,
inputs=[name_input, location_input, is_operational_input, date_input, log_textbox],
outputs=[log_textbox]
)
report_btn.click(fn=report_and_update, inputs=[log_textbox], outputs=[log_textbox])
list_btn.click(fn=list_and_update, inputs=[log_textbox], outputs=[log_textbox])
save_btn.click(fn=save_and_update, inputs=[log_textbox], outputs=[log_textbox])
load_btn.click(fn=load_and_update, inputs=[log_textbox], outputs=[log_textbox])
return app
# --- Main execution block ---
if __name__ == "__main__":
# Create a single instance of our registry that the app will use.
lighthouse_registry = LighthouseRegistry()
# Load any existing data on startup
initial_load_message = lighthouse_registry.load_from_file()
print(initial_load_message) # Print to terminal for the developer
# Create and launch the Gradio app
web_app = create_ui(lighthouse_registry)
web_app.launch(share=True) |