forked from codepath/ai110-module2show-pawpal-starter
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.py
More file actions
333 lines (292 loc) · 10.9 KB
/
app.py
File metadata and controls
333 lines (292 loc) · 10.9 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
from datetime import date, time
import streamlit as st
from pawpal_system import Owner, Pet, Scheduler, Task
DATA_FILE = "data.json"
st.set_page_config(page_title="PawPal+", page_icon="🐾", layout="centered")
st.title("🐾 PawPal+")
st.caption("Pet care planner")
if "owners" not in st.session_state:
loaded_owners = Owner.load_from_json(DATA_FILE)
st.session_state.owners = {}
for owner in loaded_owners:
st.session_state.owners[owner.name] = {
"daily_time_available": owner.daily_time_available,
"pets": {pet.name: pet for pet in owner.pets},
}
if "active_owner" not in st.session_state:
st.session_state.active_owner = (
next(iter(st.session_state.owners)) if st.session_state.owners else None
)
if "scheduler" not in st.session_state:
st.session_state.scheduler = Scheduler()
def to_minutes(t: time) -> int:
return t.hour * 60 + t.minute
def to_hhmm(minutes: int) -> str:
h = minutes // 60
m = minutes % 60
return f"{h:02d}:{m:02d}"
def format_priority(priority: str) -> str:
normalized = str(priority).strip().lower()
if normalized == "high":
return "🔴 High"
if normalized == "medium":
return "🟡 Medium"
return "🟢 Low"
def get_active_owner_record():
owner_name = st.session_state.active_owner
if not owner_name:
return None
return st.session_state.owners.get(owner_name)
def build_owner_model(owner_name: str, owner_record: dict) -> Owner:
owner = Owner(owner_name, int(owner_record["daily_time_available"]))
for pet in owner_record["pets"].values():
owner.add_pet(pet)
return owner
def save_app_state() -> None:
owners_to_save = []
for owner_name, owner_record in st.session_state.owners.items():
owners_to_save.append(build_owner_model(owner_name, owner_record))
Owner.save_to_json(owners_to_save, DATA_FILE)
st.subheader("Owners")
owner_col1, owner_col2, owner_col3 = st.columns([2, 2, 1])
with owner_col1:
new_owner_name = st.text_input("Owner name", value="Jordan", key="owner_name_input")
with owner_col2:
new_owner_daily_time = st.number_input(
"Daily time available (minutes)",
min_value=0,
max_value=1440,
value=120,
key="owner_daily_time_input",
)
with owner_col3:
st.write("")
st.write("")
if st.button("Add owner"):
cleaned_owner = new_owner_name.strip()
if not cleaned_owner:
st.error("Owner name is required.")
elif cleaned_owner in st.session_state.owners:
st.warning(f"Owner '{cleaned_owner}' already exists.")
else:
st.session_state.owners[cleaned_owner] = {
"daily_time_available": int(new_owner_daily_time),
"pets": {},
}
st.session_state.active_owner = cleaned_owner
save_app_state()
st.success(f"Added owner '{cleaned_owner}'.")
if st.session_state.owners:
st.write("Current owners:")
st.table(
[
{
"owner": name,
"daily_time_available": data["daily_time_available"],
"pets": len(data["pets"]),
}
for name, data in st.session_state.owners.items()
]
)
st.session_state.active_owner = st.selectbox(
"Active owner",
options=list(st.session_state.owners.keys()),
index=(
list(st.session_state.owners.keys()).index(st.session_state.active_owner)
if st.session_state.active_owner in st.session_state.owners
else 0
),
key="active_owner_select",
)
active_record = get_active_owner_record()
if active_record is not None:
updated_daily_time = st.number_input(
"Update active owner daily minutes",
min_value=0,
max_value=1440,
value=int(active_record["daily_time_available"]),
key="active_owner_daily_minutes",
)
if st.button("Save owner settings"):
active_record["daily_time_available"] = int(updated_daily_time)
save_app_state()
st.success(f"Updated settings for '{st.session_state.active_owner}'.")
else:
st.info("No owners yet. Add an owner to continue.")
st.divider()
active_record = get_active_owner_record()
st.subheader("Pets")
if active_record is None:
st.caption("Add and select an owner first.")
else:
pet_col1, pet_col2, pet_col3 = st.columns([2, 2, 1])
with pet_col1:
new_pet_name = st.text_input("Pet name", value="Mochi", key="pet_name_input")
with pet_col2:
new_pet_species = st.selectbox(
"Species", ["Dog", "Cat", "Other"], key="pet_species_input"
)
with pet_col3:
st.write("")
st.write("")
if st.button("Add pet"):
cleaned_pet = new_pet_name.strip()
if not cleaned_pet:
st.error("Pet name is required.")
elif cleaned_pet in active_record["pets"]:
st.warning(
f"Pet '{cleaned_pet}' already exists for owner '{st.session_state.active_owner}'."
)
else:
active_record["pets"][cleaned_pet] = Pet(cleaned_pet, new_pet_species)
save_app_state()
st.success(
f"Added pet '{cleaned_pet}' to owner '{st.session_state.active_owner}'."
)
if active_record["pets"]:
st.write(f"Current pets for {st.session_state.active_owner}:")
st.table(
[
{"name": p.name, "species": p.species}
for p in active_record["pets"].values()
]
)
else:
st.info("No pets yet for this owner.")
st.divider()
st.subheader("Tasks")
if active_record is None or not active_record["pets"]:
st.caption("Select an owner with at least one pet first.")
else:
t_col1, t_col2 = st.columns(2)
with t_col1:
task_pet = st.selectbox("Pet", list(active_record["pets"].keys()), key="task_pet")
task_title = st.text_input(
"Task description", value="Morning walk", key="task_description"
)
task_duration = st.number_input(
"Duration (minutes)",
min_value=1,
max_value=240,
value=20,
key="task_duration",
)
priority_label = st.selectbox(
"Priority", ["Low", "Medium", "High"], index=2, key="task_priority"
)
with t_col2:
task_time = st.time_input("Start time", value=time(8, 0), step=300, key="task_time")
frequency = st.selectbox(
"Frequency", ["daily", "weekly", "monthly"], index=0, key="task_frequency"
)
due = st.date_input("Due date", value=date.today(), key="task_due_date")
completed = st.checkbox("Mark as completed", key="task_completed")
if st.button("Add task"):
desc = task_title.strip()
if not desc:
st.error("Task description is required.")
else:
task = Task(
description=desc,
duration_minutes=int(task_duration),
priority=priority_label,
time=to_minutes(task_time),
pet_name=task_pet,
frequency=frequency,
completed=completed,
due_date=due,
)
active_record["pets"][task_pet].add_task(task)
save_app_state()
st.success(
f"Added task #{task.number} for {task_pet} (owner: {st.session_state.active_owner})."
)
all_tasks = []
for pet in active_record["pets"].values():
for t in pet.get_tasks():
all_tasks.append(
{
"#": t.number,
"pet": t.pet_name,
"description": t.description,
"duration": t.duration_minutes,
"priority": format_priority(t.priority),
"time": to_hhmm(t.time),
"frequency": t.frequency,
"due_date": t.due_date.isoformat(),
"completed": t.completed,
}
)
if all_tasks:
st.write(f"Current tasks for {st.session_state.active_owner}:")
st.table(all_tasks)
else:
st.info("No tasks yet for this owner.")
st.divider()
st.subheader("Scheduler")
if active_record is None:
st.caption("Select an owner first.")
elif st.button("Generate schedule"):
owner = build_owner_model(st.session_state.active_owner, active_record)
scheduler = st.session_state.scheduler
plan, explanation = scheduler.generate_plan(owner)
st.markdown(f"### Plan for {st.session_state.active_owner}")
if not plan:
st.warning("No tasks scheduled with current time constraints.")
else:
st.table(
[
{
"#": t.number,
"pet": t.pet_name,
"description": t.description,
"time": to_hhmm(t.time),
"duration": t.duration_minutes,
"priority": format_priority(t.priority),
}
for t in plan
]
)
st.markdown("### Why")
for line in explanation:
st.write(f"- {line}")
conflicts = scheduler.detect_conflicts(owner.get_all_tasks())
st.markdown("### Conflict check")
if conflicts:
for warning in conflicts:
st.warning(warning)
else:
st.success("No time conflicts detected.")
st.markdown("### Completion filters")
incomplete = scheduler.filter_by_completed(owner.get_all_tasks(), completed=False)
complete = scheduler.filter_by_completed(owner.get_all_tasks(), completed=True)
st.write(f"Incomplete tasks: {len(incomplete)}")
st.write(f"Completed tasks: {len(complete)}")
st.divider()
st.subheader("Mark Task Complete")
if active_record is None:
st.caption("Select an owner first.")
else:
owner_for_completion = build_owner_model(st.session_state.active_owner, active_record)
tasks_for_picker = sorted(owner_for_completion.get_all_tasks(), key=lambda t: t.number)
if tasks_for_picker:
option_map = {
f"#{t.number} | {t.pet_name} | {t.description} | due {t.due_date}": t.number
for t in tasks_for_picker
}
selected_label = st.selectbox(
"Choose task", list(option_map.keys()), key="complete_task_select"
)
if st.button("Mark selected task complete"):
ok = st.session_state.scheduler.mark_task_complete(
owner_for_completion, option_map[selected_label]
)
if ok:
save_app_state()
st.success(
"Task marked complete. If frequency is daily/weekly, next occurrence was created."
)
else:
st.error("Task not found.")
else:
st.caption("No tasks available for this owner.")