diff --git a/backend/src/alphapulse/calendar/europe.py b/backend/src/alphapulse/calendar/europe.py index f389547..8658710 100644 --- a/backend/src/alphapulse/calendar/europe.py +++ b/backend/src/alphapulse/calendar/europe.py @@ -307,7 +307,7 @@ def parse_announcement(family: str, body: str) -> Announcement: if p is None: raise ValueError("Eurostat GDP 公告缺少季度") period = period_of(p[1], quarter=True) - table = next((t for t in s.css("table") if "Growth rates of GDP in volume" in compact(t.xpath("string(.)").get())), None) + table = next((t for t in s.css("table") if "growth rates of gdp in volume" in compact(t.xpath("string(.)").get()).lower()), None) if table is None: raise ValueError("Eurostat 缺少实际 GDP 增长表,不能使用就业增长表") row = eurostat_row(table, "Euro area") @@ -316,7 +316,7 @@ def parse_announcement(family: str, body: str) -> Announcement: raise ValueError("Eurostat GDP 表格列口径改变") values = {"gdp_qoq": value(row[count], row[count - 1]), "gdp_yoy": value(row[-1], row[-2])} else: - p = re.search(r"In ([A-Z][a-z]+ 20\d\d), the euro area seasonally adjusted unemployment rate was\s*(" + N + r")%", text) + p = re.search(r"In ([A-Z][a-z]+ 20\d\d), the euro area seasonally[- ]adjusted unemployment rate was\s*(" + N + r")%", text) if p is None: raise ValueError("Eurostat 公告未识别到欧元区季调失业率") period = period_of(p[1]) diff --git a/backend/tests/fixtures/europe/ea_gdp_2025_01.html b/backend/tests/fixtures/europe/ea_gdp_2025_01.html new file mode 100644 index 0000000..d3d6e30 --- /dev/null +++ b/backend/tests/fixtures/europe/ea_gdp_2025_01.html @@ -0,0 +1,47 @@ + + + + +

+ GDP stable in the euro area and up by 0.1% in the EU +

+
+Preliminary flash estimate for the fourth quarter of 2024 +
+
+

In the fourth quarter of 2024, seasonally adjusted GDP remained stable in the euro area and increased by 0.1% in the EU, compared with the previous quarter, according to a preliminary flash estimate published by Eurostat, the statistical office of the European Union. In the third quarter of 2024, GDP had grown by 0.4% in both areas.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Published growth rates of GDP in volume up to 2024Q4  

(based on seasonally adjusted* data)

Percentage change compared with the previous quarter

Percentage change compared with the same quarter of the previous year

2024Q1

2024Q2

2024Q3

2024Q4

2024Q1

2024Q2

2024Q3

2024Q4

Euro area

0.3

0.2

0.4

0.0

0.4

0.5

0.9

0.9

+
diff --git a/backend/tests/fixtures/europe/ea_jobs_2025_01.html b/backend/tests/fixtures/europe/ea_jobs_2025_01.html new file mode 100644 index 0000000..42d6a19 --- /dev/null +++ b/backend/tests/fixtures/europe/ea_jobs_2025_01.html @@ -0,0 +1,142 @@ + + + + +

+ Euro area unemployment at 6.3% +

+
+November 2024 +
+
+

In November 2024, the euro area seasonally-adjusted unemployment rate was 6.3%, stable compared with October 2024 and down from 6.5% in November 2023. The EU unemployment rate was 5.9% in November 2024, also stable compared with October 2024 and down from 6.1% in November 2023. These figures are published by Eurostat, the statistical office of the European Union.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Seasonally adjusted unemployment, totals

Rates (%)

Number of persons (in thousands)

2023

2024

2023

2024

Nov

Aug

Sep

Oct

Nov

Nov

Aug

Sep

Oct

Nov

Euro area

6.5

6.3

6.3

6.3

6.3

11 152

10 850

10 862

10 858

10 819

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Seasonally adjusted youth (under 25s) unemployment

Rates (%)

Number of persons (in thousands)

2023

2024

2023

2024

Nov

Aug

Sep

Oct

Nov

Nov

Aug

Sep

Oct

Nov

Euro area

14.6

14.9

14.9

15.0

15.0

2 322

2 384

2 390

2 421

2 423

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Seasonally adjusted unemployment rates (%), by sex

Males

Females

2023

2024

2023

2024

Nov

Aug

Sep

Oct

Nov

Nov

Aug

Sep

Oct

Nov

Euro area

6.2

6.2

6.2

6.1

6.1

6.8

6.5

6.5

6.5

6.5

+
diff --git a/backend/tests/test_europe.py b/backend/tests/test_europe.py index 4d7ccef..5c1019d 100644 --- a/backend/tests/test_europe.py +++ b/backend/tests/test_europe.py @@ -25,7 +25,9 @@ def fixture(name): ("ea_hicp", "ea_cpi", "2026-09", "final", {"hicp_yoy": "3.8", "hicp_mom": "0.6", "core_hicp_yoy": "2.5"}), ("ea_hicp", "ea_flash", "2026-09", "flash", {"hicp_yoy": "3.8", "hicp_mom": "0.6", "core_hicp_yoy": "2.5"}), ("ea_jobs", "ea_jobs", "2026-08", None, {"ilo_unemployment": "6.4"}), + ("ea_jobs", "ea_jobs_2025_01", "2024-11", None, {"ilo_unemployment": "6.3"}), ("ea_gdp", "ea_gdp", "2026-Q2", "preliminary", {"gdp_qoq": "0.4", "gdp_yoy": "1"}), + ("ea_gdp", "ea_gdp_2025_01", "2024-Q4", "preliminary", {"gdp_qoq": "0", "gdp_yoy": "0.9"}), ("ea_gdp", "ea_gdp_flash", "2026-Q2", "flash", {"gdp_qoq": "0.4", "gdp_yoy": "1"}), ("ea_gdp", "ea_gdp_final", "2026-Q2", "final", {"gdp_qoq": "0.6", "gdp_yoy": "1.2"}), ("de_cpi", "de_cpi", "2026-09", "final", {"eu_cpi_yoy": "3.3", "eu_cpi_mom": "0.6", "hicp_yoy": "3.3", "hicp_mom": "0.6"}), @@ -61,6 +63,22 @@ def test_monthly_previous_is_previous_month_not_same_month_last_year(): assert result.values["eu_core_cpi_yoy"]["previous"] == "2.6" +def test_historical_eurostat_results_preserve_previous_values_and_real_publication_dates(): + gdp = parse_announcement("ea_gdp", fixture("ea_gdp_2025_01.html")) + assert gdp.at == datetime(2025, 1, 30, 10, tzinfo=UTC) + assert gdp.values["gdp_qoq"]["previous"] == "0.4" + assert gdp.values["gdp_yoy"]["previous"] == "0.9" + jobs = parse_announcement("ea_jobs", fixture("ea_jobs_2025_01.html")) + assert jobs.at == datetime(2025, 1, 7, 10, tzinfo=UTC) + assert jobs.values["ilo_unemployment"]["previous"] == "6.3" + + +def test_eurostat_gdp_does_not_take_an_employment_table_when_the_gdp_table_is_missing(): + body = fixture("ea_gdp_2025_01.html").replace("growth rates of GDP in volume", "growth rates of employment") + with pytest.raises(ValueError, match="GDP 增长表"): + parse_announcement("ea_gdp", body) + + @pytest.mark.parametrize("period,expected", [("December 2024 to February 2025", "2025-02"), ("December 2025 to February 2026", "2026-02"), ("June to August 2026", "2026-08")])