Improve conference importer and series attendance
This commit is contained in:
parent
dbce9e5358
commit
a87c9f993e
5 changed files with 87 additions and 0 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@
|
|||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('conference_series_page', series_id=item.id) }}">{{ item.name }}</a>
|
||||
{% if item.attended %}
|
||||
<span class="badge text-bg-success ms-1">attended</span>
|
||||
{% endif %}
|
||||
{% if item.url %}
|
||||
<a class="text-muted ms-1 text-decoration-none" href="{{ item.url }}">↗</a>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,35 @@ def test_normalize_dates_field_moves_legacy_dates() -> None:
|
|||
}
|
||||
|
||||
|
||||
def test_normalize_dates_field_parses_quoted_dates() -> None:
|
||||
"""Quoted ISO dates from generated YAML should become date objects."""
|
||||
conf: dict[str, typing.Any] = {
|
||||
"name": "Git Merge",
|
||||
"dates": {
|
||||
"status": "exact",
|
||||
"start": "2026-09-16",
|
||||
"end": "2026-09-17",
|
||||
},
|
||||
}
|
||||
|
||||
add_new_conference.normalize_dates_field(conf)
|
||||
|
||||
assert conf["dates"]["start"] == date(2026, 9, 16)
|
||||
assert conf["dates"]["end"] == date(2026, 9, 17)
|
||||
|
||||
|
||||
def test_validate_generated_conference_reports_missing_dates() -> None:
|
||||
"""Missing generated dates should raise a clear importer error."""
|
||||
conf: dict[str, typing.Any] = {
|
||||
"name": "Git Merge",
|
||||
"topic": "Git",
|
||||
"location": "TBC",
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="missing valid date information"):
|
||||
add_new_conference.validate_generated_conference(conf)
|
||||
|
||||
|
||||
def test_build_prompt_includes_nested_dates_and_series() -> None:
|
||||
"""The prompt should describe nested dates and known series IDs."""
|
||||
prompt = add_new_conference.build_prompt(
|
||||
|
|
|
|||
|
|
@ -118,5 +118,6 @@ def test_conference_series_pages(tmp_path: typing.Any, monkeypatch: typing.Any)
|
|||
|
||||
assert index_response.status_code == 200
|
||||
assert b"PyCascades" in index_response.data
|
||||
assert b"attended" in index_response.data
|
||||
assert detail_response.status_code == 200
|
||||
assert b"trip: Seattle Python trip" in detail_response.data
|
||||
|
|
|
|||
|
|
@ -413,12 +413,14 @@ def build_conference_series_list() -> list[StrDict]:
|
|||
next_conf = next(
|
||||
(conf for conf in linked if conf["latest_date"] >= date.today()), None
|
||||
)
|
||||
attended = any(conf.get("going") or conf.get("linked_trip") for conf in linked)
|
||||
item: StrDict = {
|
||||
"id": series_id,
|
||||
**series,
|
||||
"count": len(linked),
|
||||
"latest": latest,
|
||||
"next_conf": next_conf,
|
||||
"attended": attended,
|
||||
}
|
||||
series_items.append(item)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue