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)