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