diff --git a/agenda/thespacedevs.py b/agenda/thespacedevs.py index d5311a2..0f16e34 100644 --- a/agenda/thespacedevs.py +++ b/agenda/thespacedevs.py @@ -185,57 +185,147 @@ def get_launches( return [summarize_launch(launch) for launch in data["results"]] -def format_launch_changes(differences: StrDict) -> str: - """Convert deepdiff output to human-readable format.""" - changes = [] - # Handle value changes - if "values_changed" in differences: - for path, change in differences["values_changed"].items(): - field = path.replace("root['", "").replace("']", "").replace("root.", "") - old_val = change["old_value"] - new_val = change["new_value"] - # Format specific fields nicely - if field == "net": - try: - old_dt = datetime.fromisoformat(old_val.replace("Z", "+00:00")) - new_dt = datetime.fromisoformat(new_val.replace("Z", "+00:00")) - changes.append( - "Launch time changed from " - + old_dt.strftime("%d %b %Y at %H:%M UTC") - + " to " - + new_dt.strftime("%d %b %Y at %H:%M UTC") - ) - except: - changes.append(f"Launch time changed from {old_val} to {new_val}") - elif field == "name": - changes.append(f"Mission name changed from '{old_val}' to '{new_val}'") - elif field == "probability": - if old_val is None: - changes.append(f"Launch probability set to {new_val}%") - elif new_val is None: - changes.append("Launch probability removed") - else: - changes.append( - f"Launch probability changed from {old_val}% to {new_val}%" - ) - elif "status" in field: +def format_date(dt: datetime) -> str: + """Human readable date.""" + return dt.strftime("%d %b %Y at %H:%M UTC") + + +def format_datetime_change(field_name: str, old_val: str, new_val: str) -> str: + """Format a datetime field change with proper error handling.""" + try: + old_dt = datetime.fromisoformat(old_val.replace("Z", "+00:00")) + new_dt = datetime.fromisoformat(new_val.replace("Z", "+00:00")) + return ( + f"{field_name} changed from {format_date(old_dt)} to {format_date(new_dt)}" + ) + except (ValueError, AttributeError): + return f"{field_name} changed from {old_val} to {new_val}" + + +def format_datetime_update(field_name: str, new_val: str) -> str: + """Format a datetime field update (showing only the new value).""" + try: + new_dt = datetime.fromisoformat(new_val.replace("Z", "+00:00")) + return f"{field_name}: {format_date(new_dt)}" + except (ValueError, AttributeError): + return f"{field_name}: {new_val}" + + +def format_probability_change(old_val: int, new_val: int) -> str: + """Format probability field changes.""" + if old_val is None: + return f"Launch probability set to {new_val}%" + elif new_val is None: + return "Launch probability removed" + else: + return f"Launch probability changed from {old_val}% to {new_val}%" + + +def handle_value_changes(differences: StrDict) -> tuple[list[str], set[str]]: + """Handle value changes in launch data.""" + changes: list[str] = [] + processed_paths: set[str] = set() + + # Skip fields that aren't useful to users + SKIP_FIELDS = { + "agency_launch_attempt_count", + "agency_launch_attempt_count_year", + "location_launch_attempt_count", + "location_launch_attempt_count_year", + "pad_launch_attempt_count", + "pad_launch_attempt_count_year", + "orbital_launch_attempt_count", + "orbital_launch_attempt_count_year", + } + + if "values_changed" not in differences: + return changes, processed_paths + + for path, change in differences["values_changed"].items(): + if any(path.startswith(processed) for processed in processed_paths): + continue + + field = path.replace("root['", "").replace("']", "").replace("root.", "") + old_val = change["old_value"] + new_val = change["new_value"] + + match field: + case "status['name']": changes.append(f"Status changed from '{old_val}' to '{new_val}'") - else: - changes.append( - f"{field.replace('_', ' ').title()} changed " - + f"from '{old_val}' to '{new_val}'" + processed_paths.update( + [ + "root['status']['id']", + "root['status']['name']", + "root['status']['abbrev']", + "root['status']['description']", + ] ) - # Handle additions + + case x if x.startswith("status["): + continue + + case "net_precision['name']": + changes.append( + f"Launch precision changed from '{old_val}' to '{new_val}'" + ) + processed_paths.update( + [ + "root['net_precision']['id']", + "root['net_precision']['name']", + "root['net_precision']['abbrev']", + "root['net_precision']['description']", + ] + ) + + case x if x.startswith("net_precision["): + continue + + case "net": + changes.append(format_datetime_change("Launch time", old_val, new_val)) + + case "window_start": + changes.append( + format_datetime_change("Launch window start", old_val, new_val) + ) + + case "window_end": + changes.append( + format_datetime_change("Launch window end", old_val, new_val) + ) + + case "last_updated": + changes.append(format_datetime_update("Last updated", new_val)) + + case "name": + changes.append(f"Mission name changed from '{old_val}' to '{new_val}'") + + case "probability": + changes.append(format_probability_change(old_val, new_val)) + + case x if x in SKIP_FIELDS: + continue + + case _: + changes.append(f"{field} changed from '{old_val}' to '{new_val}'") + + return changes, processed_paths + + +def format_launch_changes(differences: StrDict) -> str: + """Convert deepdiff output to human-readable format using match/case.""" + changes, processed_paths = handle_value_changes(differences) + + # Handle other difference types... if "dictionary_item_added" in differences: for path in differences["dictionary_item_added"]: field = path.replace("root['", "").replace("']", "").replace("root.", "") changes.append(f"New field added: {field.replace('_', ' ').title()}") - # Handle removals + if "dictionary_item_removed" in differences: for path in differences["dictionary_item_removed"]: field = path.replace("root['", "").replace("']", "").replace("root.", "") changes.append(f"Field removed: {field.replace('_', ' ').title()}") - # Handle type changes + if "type_changes" in differences: for path, change in differences["type_changes"].items(): field = path.replace("root['", "").replace("']", "").replace("root.", "") @@ -243,6 +333,7 @@ def format_launch_changes(differences: StrDict) -> str: f"{field.replace('_', ' ').title()} type changed " + f"from {change['old_type'].__name__} to {change['new_type'].__name__}" ) + return ( "\n".join(f"• {change}" for change in changes) if changes