diff --git a/agenda/add_new_conference.py b/agenda/add_new_conference.py index 1a6820f..e74455c 100644 --- a/agenda/add_new_conference.py +++ b/agenda/add_new_conference.py @@ -502,6 +502,42 @@ def parse_yaml_datetime(value: typing.Any) -> datetime | None: return None +def parse_yaml_date_value(value: typing.Any) -> date | datetime | None: + """Convert YAML date/datetime strings to date-like values.""" + if isinstance(value, datetime): + return value + + if isinstance(value, date): + return value + + if not isinstance(value, str): + return None + + try: + if " " in value or "T" in value: + return datetime.fromisoformat(value) + return date.fromisoformat(value) + except ValueError: + return None + + +def normalize_date_values(conf: dict[str, typing.Any]) -> None: + """Normalize quoted ISO date/datetime values produced by the LLM.""" + dates = conf.get("dates") + if isinstance(dates, dict): + for field in ("start", "end", "earliest", "latest"): + if field in dates: + parsed = parse_yaml_date_value(dates[field]) + if parsed is not None: + dates[field] = parsed + + for field in ("start", "end"): + if field in conf: + parsed = parse_yaml_date_value(conf[field]) + if parsed is not None: + conf[field] = parsed + + def same_type_as_start( start_value: typing.Any, new_dt: datetime, @@ -529,6 +565,7 @@ def same_type_as_start( def normalize_dates_field(conf: dict[str, typing.Any]) -> None: """Move legacy top-level date fields into the nested dates mapping.""" + normalize_date_values(conf) raw_dates = conf.get("dates") dates = raw_dates if isinstance(raw_dates, dict) else None @@ -550,6 +587,20 @@ def normalize_dates_field(conf: dict[str, typing.Any]) -> None: conf.pop("start", None) conf.pop("end", None) conf.pop("date_status", None) + normalize_date_values(conf) + + +def validate_generated_conference(conf: dict[str, typing.Any]) -> None: + """Validate generated conference YAML before inserting it.""" + try: + conference_date_fields(conf) + except ValueError as exc: + generated_yaml = yaml.dump(conf, sort_keys=False, allow_unicode=True).strip() + raise ValueError( + "Generated conference YAML is missing valid date information. " + "Expected nested `dates:` with exact/tentative start/end or " + f"approximate earliest/latest.\n\nGenerated YAML:\n{generated_yaml}" + ) from exc def maybe_extract_explicit_end_time(source_text: str) -> int | None: @@ -661,6 +712,7 @@ def add_new_conference(url: str, yaml_path: str) -> bool: normalize_dates_field(new_conf) normalise_end_field(new_conf, source_text) normalize_dates_field(new_conf) + validate_generated_conference(new_conf) if detected_coordinates is not None: new_conf["latitude"] = detected_coordinates[0] diff --git a/templates/conference_series_list.html b/templates/conference_series_list.html index a39890d..f9ccc5f 100644 --- a/templates/conference_series_list.html +++ b/templates/conference_series_list.html @@ -21,6 +21,9 @@