Add official monthly ADP calendar collection
This commit is contained in:
1 parent
8d6cbeeb40
commit
4184bc68e6
14 files changed
+631
-9
No files matched your search
@@ -0,0 +1,25 @@
|
||||
"""Register the official monthly ADP calendar source."""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "f7a0bc354de2"
|
||||
down_revision = "e6f9ab243cd1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
source = sa.table("sources", *[sa.column(key, kind) for key, kind in [
|
||||
("code", sa.String), ("name", sa.String), ("list_url", sa.String), ("collector", sa.String),
|
||||
("enabled", sa.Boolean), ("interval_seconds", sa.Integer), ("backfill_hours", sa.Integer),
|
||||
("consecutive_failures", sa.Integer),
|
||||
]])
|
||||
op.bulk_insert(source, [{"code": "adp", "name": "ADP·月度私营就业报告",
|
||||
"list_url": "https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report",
|
||||
"collector": "calendar", "enabled": True, "interval_seconds": 3600,
|
||||
"backfill_hours": 72, "consecutive_failures": 0}])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Existing raw responses prevent source deletion through their foreign key.
|
||||
op.execute("DELETE FROM sources WHERE code = 'adp'")
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Read monthly ADP releases; weekly NER Pulse estimates are a separate series."""
|
||||
import re
|
||||
from datetime import UTC, date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
from parsel import Selector
|
||||
|
||||
from alphapulse.calendar.archives import Announcement
|
||||
from alphapulse.calendar.parsers import EASTERN, MONTHS, Scheduled, compact
|
||||
|
||||
INDEX = "https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report"
|
||||
TITLE = "ADP National Employment Report"
|
||||
MONTH = "(?:" + "|".join(MONTHS) + ")"
|
||||
NUMBER = r"-?\d{1,3}(?:,\d{3})*"
|
||||
NEXT_RELEASE = re.compile(r"The (" + MONTH + r") (20\d\d) ADP[^.]*?National Employment Report[^.]*?will be released (?:at 8:15 a\.m\. ET on (" + MONTH + r" \d{1,2}, 20\d\d)|on (" + MONTH + r" \d{1,2}, 20\d\d) at 8:15 a\.m\. ET)")
|
||||
|
||||
|
||||
def monthly_title(title: str) -> bool:
|
||||
return bool(re.match(r"ADP National Employment Report: Private[- ]Sector Employment (?:Increased|Shed|Decreased|Unchanged)", title, re.I))
|
||||
|
||||
|
||||
def release_links(body: str, base: str, start: date, end: date) -> tuple[list[str], str | None]:
|
||||
sel = Selector(text=body)
|
||||
rows = sel.css(".wd_item_list .wd_item")
|
||||
if not rows:
|
||||
raise ValueError("ADP 官方新闻索引缺少公告列表")
|
||||
links, dates = [], []
|
||||
for row in rows:
|
||||
day = datetime.strptime(compact(row.css(".wd_date").xpath("string(.)").get() or ""), "%b %d, %Y").date()
|
||||
dates.append(day)
|
||||
title = compact(row.css(".wd_title").xpath("string(.)").get() or "")
|
||||
if start <= day <= end and monthly_title(title):
|
||||
url = urljoin(base, row.css(".wd_title a::attr(href)").get() or "")
|
||||
if urlparse(url).scheme != "https" or urlparse(url).hostname != "mediacenter.adp.com" or "ADP-National-Employment-Report-" not in urlparse(url).path:
|
||||
raise ValueError("ADP 公告链接不是官方月报地址")
|
||||
links.append(url)
|
||||
if dates != sorted(dates, reverse=True):
|
||||
raise ValueError("ADP 官方新闻索引不再按公布日期倒序")
|
||||
following = sel.css(".wd_page_next a::attr(href)").get()
|
||||
# The index is newest first; continue through pages containing only weekly estimates.
|
||||
following = urljoin(base, following) if following and min(dates) >= start else None
|
||||
if following and (urlparse(following).hostname != "mediacenter.adp.com" or urlparse(following).path != "/press-releases" or urlparse(following).scheme != "https"):
|
||||
raise ValueError("ADP 分页链接不属于官方新闻索引")
|
||||
return list(dict.fromkeys(links)), following
|
||||
|
||||
|
||||
def report(body: str) -> Announcement:
|
||||
sel = Selector(text=body)
|
||||
titles = sel.css(".wd_title").xpath("string(.)").getall()
|
||||
texts = sel.css(".wd_news_body").xpath("string(.)").getall()
|
||||
dates = sel.css(".wd_date").xpath("string(.)").getall()
|
||||
if len(titles) != 1 or len(texts) != 1 or len(dates) != 1 or not monthly_title(compact(titles[0])):
|
||||
raise ValueError("ADP 响应不是唯一的月度就业公告")
|
||||
text = compact(texts[0])
|
||||
period = re.search(r"(" + MONTH + r") (20\d\d) Report Highlights", text)
|
||||
reading = re.search(r"Change in U\.S\. Private Employment:\s*(" + NUMBER + r")(?![\d,])", text)
|
||||
if period is None or reading is None:
|
||||
raise ValueError("ADP 月报缺少统计期或私营就业净变动")
|
||||
month = MONTHS[period[1]]
|
||||
statistical = date(int(period[2]), month, 1)
|
||||
day = datetime.strptime(compact(dates[0]), "%b %d, %Y").date()
|
||||
# Monthly reports explicitly announce the next release at 8:15 a.m. ET.
|
||||
# Require that cadence in each document instead of guessing an unknown time.
|
||||
if NEXT_RELEASE.search(text) is None:
|
||||
raise ValueError("ADP 公告缺少明确的月报美东公布时刻")
|
||||
if not 0 <= (day - statistical).days <= 45:
|
||||
raise ValueError("ADP 统计月与公告日期不符")
|
||||
title_month = re.search(r"Jobs in (" + MONTH + r")", compact(titles[0]), re.I)
|
||||
if title_month is None or title_month[1].lower() != period[1].lower():
|
||||
raise ValueError("ADP 标题与正文统计月不符")
|
||||
headline = re.search(r"Employment (Increased by|Shed|Decreased by) (" + NUMBER + r") Jobs", compact(titles[0]), re.I)
|
||||
headline_value = Decimal(headline[2].replace(",", "")) * (1 if headline[1].lower() == "increased by" else -1) if headline else None
|
||||
if headline_value != Decimal(reading[1].replace(",", "")):
|
||||
raise ValueError("ADP 标题与正文的就业净变动不符")
|
||||
previous_month = (statistical - timedelta(days=1)).strftime("%B")
|
||||
previous = re.search(r"The " + previous_month + r" total (?:number of )?jobs added was revised from " + NUMBER + r" to (" + NUMBER + r")(?![\d,])", text)
|
||||
values = {"actual": format(Decimal(reading[1].replace(",", "")) / 10000, "f"),
|
||||
"previous": format(Decimal(previous[1].replace(",", "")) / 10000, "f") if previous else None}
|
||||
if previous:
|
||||
values["previous_basis"] = "ADP 本次月报列示的上月修订净变动"
|
||||
return Announcement(datetime.combine(day, time(8, 15), EASTERN).astimezone(UTC), statistical.strftime("%Y-%m"), None,
|
||||
{"adp_jobs": values})
|
||||
|
||||
|
||||
def next_release(body: str) -> Scheduled:
|
||||
text = compact(" ".join(Selector(text=body).css(".wd_news_body").xpath("string(.)").getall()))
|
||||
match = NEXT_RELEASE.search(text)
|
||||
if match is None:
|
||||
raise ValueError("ADP 公告缺少明确的下次月报日期与美东时刻")
|
||||
day = datetime.strptime(match[3] or match[4], "%B %d, %Y").date()
|
||||
period = date(int(match[2]), MONTHS[match[1]], 1)
|
||||
if not 0 <= (day - period).days <= 45:
|
||||
raise ValueError("ADP 下次公布日期与统计月不符")
|
||||
current = re.search(r"(" + MONTH + r") (20\d\d) Report Highlights", text)
|
||||
if current is None:
|
||||
raise ValueError("ADP 缺少本次月报统计期")
|
||||
expected_next = (date(int(current[2]), MONTHS[current[1]], 28) + timedelta(days=4)).replace(day=1)
|
||||
if period != expected_next:
|
||||
raise ValueError("ADP 下次月报不是相邻统计月")
|
||||
published = compact(Selector(text=body).css(".wd_date").xpath("string(.)").get() or "")
|
||||
if day <= datetime.strptime(published, "%b %d, %Y").date():
|
||||
raise ValueError("ADP 下次月报日期不晚于本次公告")
|
||||
return Scheduled(f"adp-{period:%Y-%m}", "adp_jobs", TITLE, datetime.combine(day, time(8, 15), EASTERN).astimezone(UTC), period=period.strftime("%Y-%m"))
|
||||
|
||||
|
||||
def schedule(body: str, url: str) -> list[Scheduled]:
|
||||
current = report(body)
|
||||
return [Scheduled(f"adp-{current.period}", "adp_jobs", TITLE, current.at, period=current.period, release_url=url), next_release(body)]
|
||||
@@ -24,7 +24,7 @@ from parsel import Selector
|
||||
from sqlalchemy import select, text
|
||||
|
||||
from alphapulse.calendar.archives import Announcement, parse_announcements, parse_umich_report
|
||||
from alphapulse.calendar import dol, eia, europe
|
||||
from alphapulse.calendar import adp, dol, eia, europe
|
||||
from alphapulse.calendar.catalog import UMICH_CSV, UMICH_HISTORY, UMICH_HOME, UMICH_INFO, UMICH_REPORTS
|
||||
from alphapulse.calendar.parsers import Scheduled, bea_family, compact
|
||||
from alphapulse.calendar.service import record_revision, save_raw, save_result, save_schedule, save_series
|
||||
@@ -39,6 +39,7 @@ HOSTS = {"bls": {"www.bls.gov", "www.dol.gov"}, "bea": {"www.bea.gov"},
|
||||
US_SOURCES = frozenset(HOSTS)
|
||||
HOSTS["dol"] = {"www.dol.gov", "oui.doleta.gov"}
|
||||
HOSTS["eia"] = {"www.eia.gov", "ir.eia.gov"}
|
||||
HOSTS["adp"] = {"mediacenter.adp.com"}
|
||||
HOSTS.update(EUROPE_HOSTS)
|
||||
|
||||
|
||||
@@ -202,6 +203,24 @@ async def fetch_documents(start: date, end: date, known: set[str], errors: list[
|
||||
documents.append(await fetch("dol", dol.LATEST))
|
||||
except (httpx.HTTPError, ValueError) as exc:
|
||||
report_error("dol", dol.LATEST, exc)
|
||||
if "adp" in selected:
|
||||
url = adp.INDEX
|
||||
visited = set()
|
||||
while url:
|
||||
try:
|
||||
if url in visited:
|
||||
raise ValueError("ADP 官方分页形成循环")
|
||||
visited.add(url)
|
||||
page = await fetch("adp", url)
|
||||
links, following = adp.release_links(page.body.decode(), url, start, end)
|
||||
for link in links:
|
||||
if link not in known:
|
||||
documents.append(await fetch("adp", link))
|
||||
known.add(link)
|
||||
url = following
|
||||
except (httpx.HTTPError, ValueError) as exc:
|
||||
report_error("adp", str(exc.request.url) if isinstance(exc, httpx.HTTPError) else url, exc)
|
||||
break
|
||||
if "eia" in selected:
|
||||
try:
|
||||
documents.append(await fetch("eia", eia.SCHEDULE))
|
||||
@@ -310,7 +329,7 @@ async def run(start: date, end: date, *, commit: bool = False, stored_only: bool
|
||||
RawResponse.source_code.in_(selected), RawResponse.status == 200,
|
||||
(RawResponse.url.contains("/news.release/") | RawResponse.url.contains("/newsroom/economicdata/") |
|
||||
RawResponse.url.contains("/news/202") | RawResponse.url.contains("/pressreleases/monetary") |
|
||||
RawResponse.source_code.in_(("umich", "dol", "eia")) | RawResponse.source_code.in_(EUROPE_HOSTS)),
|
||||
RawResponse.source_code.in_(("umich", "dol", "eia", "adp")) | RawResponse.source_code.in_(EUROPE_HOSTS)),
|
||||
).distinct(RawResponse.source_code, RawResponse.url, RawResponse.body_sha256)
|
||||
.order_by(RawResponse.source_code, RawResponse.url, RawResponse.body_sha256, RawResponse.id))).all()
|
||||
documents = [Document(r.source_code, r.url, gzip.decompress(body), r.fetched_at, r.id, r.headers) for r, body in rows]
|
||||
@@ -350,6 +369,12 @@ async def run(start: date, end: date, *, commit: bool = False, stored_only: bool
|
||||
announcements.append((document, "umich", announcement, schedules[identity][0].title))
|
||||
for document in sorted(documents, key=lambda d: d.captured_at):
|
||||
try:
|
||||
if document.source == "adp":
|
||||
if "ADP-National-Employment-Report-" in urlparse(document.url).path:
|
||||
announcement = adp.report(document.body.decode())
|
||||
if begin <= announcement.at < finish:
|
||||
announcements.append((document, "adp_jobs", announcement, adp.TITLE))
|
||||
continue
|
||||
if document.source == "eia":
|
||||
if urlparse(document.url).path.endswith("/table1.csv"):
|
||||
if eia_schedule is None:
|
||||
|
||||
@@ -39,6 +39,7 @@ INDICATORS = {
|
||||
"umich_sentiment": Indicator("密歇根大学消费者信心指数", 3, "点"),
|
||||
"initial_claims": Indicator("初请失业金人数(季调)", 4, "万人"),
|
||||
"continued_claims": Indicator("续请失业金人数(季调)", 3, "万人"),
|
||||
"adp_jobs": Indicator("ADP 私营部门就业净变动(月度)", 4, "万人"),
|
||||
"eia_crude": Indicator("EIA 商业原油库存变动(不含战略储备)", 4, "万桶"),
|
||||
"eia_gasoline": Indicator("EIA 汽油库存变动", 3, "万桶"),
|
||||
"eia_distillate": Indicator("EIA 精炼油库存变动", 3, "万桶"),
|
||||
@@ -53,6 +54,7 @@ FAMILIES = {
|
||||
"umich": Family("US", "umich", ("umich_sentiment",)),
|
||||
"initial_claims": Family("US", "dol", ("initial_claims",)),
|
||||
"continued_claims": Family("US", "dol", ("continued_claims",)),
|
||||
"adp_jobs": Family("US", "adp", ("adp_jobs",)),
|
||||
"eia_stocks": Family("US", "eia", ("eia_crude", "eia_gasoline", "eia_distillate")),
|
||||
}
|
||||
STAGES = {"preliminary": "初值", "final": "终值", "initial": "首发值", "updated": "更新估计", "advance": "初值", "second": "二次估计", "third": "终值"}
|
||||
|
||||
@@ -12,7 +12,7 @@ from parsel import Selector
|
||||
from sqlalchemy import select
|
||||
|
||||
from alphapulse.calendar.catalog import SCHEDULE_URLS, UMICH_CSV, UMICH_HISTORY, UMICH_HOME, UMICH_INFO
|
||||
from alphapulse.calendar import dol, eia, europe
|
||||
from alphapulse.calendar import adp, dol, eia, europe
|
||||
from alphapulse.calendar.parsers import EASTERN, MONTHS, bea_release_link, parse_fed_calendar, parse_ics, parse_values, release_date, bls_periods, release_metadata
|
||||
from alphapulse.calendar.service import apply_bls_periods, record_revision, next_poll, save_raw, save_result, save_schedule, save_series
|
||||
from alphapulse.calendar.umich import current_result, document_links, monthly_series, parse_schedule, pdf_text
|
||||
@@ -64,7 +64,7 @@ class CalendarWorker:
|
||||
# 链接来自公开响应,限制到对应官方域名,避免外部内容扩大访问范围。
|
||||
allowed = {"bls": {"www.bls.gov"}, "bea": {"www.bea.gov", "apps.bea.gov"}, "fed": {"www.federalreserve.gov"},
|
||||
"umich": {"www.sca.isr.umich.edu", "data.sca.isr.umich.edu"}, "dol": {"www.dol.gov", "oui.doleta.gov"},
|
||||
"eia": {"www.eia.gov", "ir.eia.gov"}, **HOSTS}
|
||||
"eia": {"www.eia.gov", "ir.eia.gov"}, "adp": {"mediacenter.adp.com"}, **HOSTS}
|
||||
if urlparse(url).scheme != "https" or urlparse(url).hostname not in allowed[source]:
|
||||
raise ValueError("公告链接不是对应机构的官方 HTTPS 地址")
|
||||
async with self.fetch_slots:
|
||||
@@ -88,6 +88,7 @@ class CalendarWorker:
|
||||
urls += list(europe.SCHEDULES.items())
|
||||
urls += [("dol", dol.SCHEDULE)]
|
||||
urls += [("eia", eia.SCHEDULE)]
|
||||
urls += [("adp", adp.INDEX)]
|
||||
# 启动时明确各来源尚未就绪;月份滚动后移除不再检查的旧月状态。
|
||||
self.sources = {url: self.sources.get(url, {"source": source, "checked_at": now.isoformat(), "error": "尚未同步"})
|
||||
for source, url in urls}
|
||||
@@ -99,7 +100,17 @@ class CalendarWorker:
|
||||
fetch_url = europe.eurostat_calendar_url(now) if source == "eurostat" else url
|
||||
response, raw_id = await self.fetch(source, fetch_url, query=europe.INSEE_QUERY if source == "insee" else None)
|
||||
response.raise_for_status()
|
||||
if source in ("dol", "eia"):
|
||||
if source == "adp":
|
||||
links, _ = adp.release_links(response.text, url, datetime.min.date(), now.astimezone(EASTERN).date())
|
||||
if not links:
|
||||
raise ValueError("ADP 官方新闻索引缺少近期月报")
|
||||
report, evidence = await self.fetch(source, links[0])
|
||||
report.raise_for_status()
|
||||
events = adp.schedule(report.text, str(report.url))
|
||||
async with SessionLocal() as session:
|
||||
await save_schedule(session, source, url, events, evidence, datetime.now(UTC), withdraw_missing=False)
|
||||
await session.commit()
|
||||
elif source in ("dol", "eia"):
|
||||
events = (dol if source == "dol" else eia).schedule(response.text, now)
|
||||
async with SessionLocal() as session:
|
||||
await save_schedule(session, source, url, events, raw_id, datetime.now(UTC), withdraw_missing=False)
|
||||
@@ -179,6 +190,24 @@ class CalendarWorker:
|
||||
await session.commit()
|
||||
|
||||
async def result_page(self, row: EconomicRelease) -> tuple[httpx.Response, int] | None:
|
||||
if row.source == "adp":
|
||||
if row.release_url:
|
||||
url = row.release_url
|
||||
else:
|
||||
index, _ = await self.fetch("adp", adp.INDEX)
|
||||
index.raise_for_status()
|
||||
links, _ = adp.release_links(index.text, adp.INDEX, row.scheduled_at.astimezone(EASTERN).date(), row.scheduled_at.astimezone(EASTERN).date())
|
||||
if not links:
|
||||
return None
|
||||
if len(links) != 1:
|
||||
raise ValueError("ADP 同日月度公告不唯一")
|
||||
url = links[0]
|
||||
response, raw_id = await self.fetch("adp", url)
|
||||
response.raise_for_status()
|
||||
announcement = adp.report(response.text)
|
||||
if announcement.at != row.scheduled_at or announcement.period != row.period:
|
||||
raise ValueError("ADP 公告时间或统计月与所选日程不符")
|
||||
return response, raw_id
|
||||
if row.source == "eia":
|
||||
if self.eia_schedule_body is None:
|
||||
schedule, _ = await self.fetch("eia", eia.SCHEDULE)
|
||||
@@ -310,7 +339,10 @@ class CalendarWorker:
|
||||
page = await self.result_page(row)
|
||||
if page is not None:
|
||||
response, raw_id = page
|
||||
if row.source == "eia":
|
||||
if row.source == "adp":
|
||||
announcement = adp.report(response.text)
|
||||
period, stage, values = announcement.period, announcement.stage, announcement.values
|
||||
elif row.source == "eia":
|
||||
announcement = eia.report(response.text, self.eia_schedule_body)
|
||||
period, stage, values = announcement.period, announcement.stage, announcement.values
|
||||
elif row.source == "dol":
|
||||
|
||||
@@ -19,7 +19,7 @@ FEEDS = {
|
||||
"insee": ("https://www.insee.fr/fr/flux/3",),
|
||||
}
|
||||
REGIONS = {"wscn_news": "CN", "wscn_live": "CN", "cls_depth": "CN", "cls_telegraph": "CN", "coindesk": "GLOBAL",
|
||||
"bls": "US", "bea": "US", "fed": "US", "umich": "US", "dol": "US", "eia": "US", "ecb": "EA", "eurostat": "EA", "boe": "GB", "ons": "GB", "destatis": "DE", "insee": "FR"}
|
||||
"bls": "US", "bea": "US", "fed": "US", "umich": "US", "dol": "US", "eia": "US", "adp": "US", "ecb": "EA", "eurostat": "EA", "boe": "GB", "ons": "GB", "destatis": "DE", "insee": "FR"}
|
||||
HOSTS = {"ecb": {"www.ecb.europa.eu"}, "eurostat": {"ec.europa.eu"}, "boe": {"www.bankofengland.co.uk"},
|
||||
"ons": {"www.ons.gov.uk"}, "destatis": {"www.destatis.de"}, "insee": {"www.insee.fr"}}
|
||||
LANGUAGES = {"destatis": "de", "insee": "fr"}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<div class="wd_title wd_language_left">ADP National Employment Report: Private Sector Employment Increased by 122,000 Jobs in December; Annual Pay was Up 4.6%</div>
|
||||
<div class="wd_date">Jan 8, 2025</div>
|
||||
<div class="wd_body wd_news_body fr-view" wd_resize="formatNews" wd_print_url="https://mediacenter.adp.com/2025-01-08-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-122,000-Jobs-in-December-Annual-Pay-was-Up-4-6?printable=1">
|
||||
<p><span class="legendSpanClass"><span class="xn-location">ROSELAND, N.J.</span></span>, <span class="legendSpanClass"><span class="xn-chron">Jan. 8, 2025</span></span> /<a href="http://www.prnewswire.com/" target="_blank">PRNewswire</a>/ -- Private sector employment increased by 122,000 jobs in December and annual pay was up 4.6 percent year-over-year, according to the December <i><a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">ADP<sup>®</sup> National Employment Report™</a></i> produced by ADP Research in collaboration with the Stanford Digital Economy Lab ("<span class="xn-person">Stanford Lab</span>"). The ADP National Employment Report is an independent measure and high-frequency view of the private-sector labor market based on actual, anonymized payroll data of more than 25 million U.S. employees.</p>
|
||||
<p>The jobs report and pay insights use ADP's fine-grained anonymized and aggregated payroll data to provide a representative picture of the private-sector labor market. The report details the current month's total private employment change, and weekly job data from the previous month. Because the underlying ADP payroll databases are continuously updated, the report provides a high-frequency, near real-time measure of U.S. employment. This measure reflects the number of employees on ADP client payrolls (Payroll Employment) to provide a richer understanding of the labor market. ADP's pay measure uniquely captures the earnings of a cohort of almost 10 million employees over a 12-month period.</p>
|
||||
<p>"The labor market downshifted to a more modest pace of growth in the final month of 2024, with a slowdown in both hiring and pay gains," said <span class="xn-person">Nela Richardson</span>, chief economist, ADP. "Health care stood out in the second half of the year, creating more jobs than any other sector."</p>
|
||||
<p><b><u><span class="xn-chron">December 2024</span> Report Highlights</u></b><b>*</b></p>
|
||||
<p>View the <i>ADP National Employment Report</i> and interactive charts at <a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">www.adpemploymentreport.com</a>.</p>
|
||||
<p><b><u>JOBS REPORT</u></b></p>
|
||||
<p><b>Private employers added 122,000 jobs in December<br></b>Hiring slowed in several industries. Employment in manufacturing shrank for the third straight month.</p>
|
||||
<p><b><u>Change in U.S. Private Employment</u></b><b>: 122,000</b></p>
|
||||
<p><b>Change by Industry Sector</b></p>
|
||||
<p>- <b>Goods-producing:</b> <b>10,000</b></p>
|
||||
<ul type="disc"><li>Natural resources/mining -6,000</li><li>Construction 27,000</li><li>Manufacturing -11,000</li></ul><p>- <b>Service-providing:</b> <b>112,000</b></p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 8,000</li><li>Information 5,000</li><li>Financial activities 12,000</li><li>Professional/business services -5,000</li><li>Education/health services 57,000</li><li>Leisure/hospitality 22,000</li><li>Other services 13,000</li></ul><p><b>Change by U.S. Regions </b></p>
|
||||
<p>- <b>Northeast: 19,000</b></p>
|
||||
<ul type="disc"><li>New England 6,000</li><li>Middle Atlantic 13,000</li></ul><p>- <b>Midwest: 7,000</b></p>
|
||||
<ul type="disc"><li>East North Central -2,000</li><li>West North Central 9,000</li></ul><p>- <b>South: 19,000</b></p>
|
||||
<ul type="disc"><li>South Atlantic -1,000</li><li>East South Central 4,000</li><li>West South Central 16,000</li></ul><p>- <b>West: 82,000</b></p>
|
||||
<ul type="disc"><li>Mountain 26,000</li><li>Pacific 56,000</li></ul><p><b>Change by Establishment Size </b></p>
|
||||
<p>- <b>Small establishments: 5,000</b></p>
|
||||
<ul type="disc"><li>1-19 employees -1,000</li><li>20-49 employees 6,000</li></ul><p>- <b>Medium establishments: 9,000</b></p>
|
||||
<ul type="disc"><li>50-249 employees -2,000</li><li>250-499 employees 11,000</li></ul><p>- <b>Large establishments: 97,000</b></p>
|
||||
<ul type="disc"><li>500+ employees 97,000</li></ul><p><b><u>PAY INSIGHTS </u></b></p>
|
||||
<p><b>Pay gains slowed in December<br></b>Year-over-year pay growth for job-stayers slowed to 4.6 percent, the slowest pace of gains since <span class="xn-chron">July 2021</span>. Pay growth for job-changers was 7.1 percent, a slight decline from November.</p>
|
||||
<p><b><u>Median Change in Annual Pay (<i>ADP matched person sample</i>)</u></b></p>
|
||||
<p><b>- Job-Stayers </b><b>4.6%</b></p>
|
||||
<p>- <b>Job-Changers 7</b><b>.1%</b></p>
|
||||
<p><b>Median Change in Annual Pay for Job-Stayers by Industry Sector </b></p>
|
||||
<p>- <b>Goods-producing:</b> </p>
|
||||
<ul type="disc"><li>Natural resources/mining 3.8%</li><li>Construction 5.1%</li><li>Manufacturing 4.5%</li></ul><p>- <b>Service-providing:</b> </p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 4.4%</li><li>Information 4.4%</li><li>Financial activities 4.9%</li><li>Professional/business services 4.5%</li><li>Education/health services 4.9%</li><li>Leisure/hospitality 4.6%</li><li>Other services 4.7%</li></ul><p><b>Median Change in Annual Pay for Job-Stayers by Firm Size</b></p>
|
||||
<p>- <b>Small firms: </b></p>
|
||||
<ul type="disc"><li>1-19 employees 3.9%</li><li>20-49 employees 4.6%</li></ul><p>- <b>Medium firms: </b></p>
|
||||
<ul type="disc"><li>50-249 employees 4.8%</li><li>250-499 employees 4.8%</li></ul><p>- <b>Large firms: </b></p>
|
||||
<ul type="disc"><li>500+ employees 4.6%</li></ul><p><b>To see Pay Insights by U.S. State, Gender, and Age for Job-Stayers, visit <a href="https://payinsights.adp.com/" target="_blank" rel="nofollow">here</a>: </b></p>
|
||||
<p>* Sum of components may not equal total due to rounding.</p>
|
||||
<p>The November total number of jobs added remained unchanged at 146,000. The historical data file and weekly data for the previous month are available at <a href="https://adpemploymentreport.com/" target="_blank" rel="nofollow">https://adpemploymentreport.com/</a>.</p>
|
||||
<p>To subscribe to monthly email alerts or obtain additional information about the ADP National Employment Report, including employment and pay data, interactive charts, methodology, and a calendar of release dates, please visit <a href="https://adpemploymentreport.com/" target="_blank" rel="nofollow">https://adpemploymentreport.com/</a>. </p>
|
||||
<p>The <span class="xn-chron">January 2025</span> <i>ADP National Employment Report </i>will be released at <span class="xn-chron">8:15 a.m. ET</span> on <span class="xn-chron">February 5, 2025</span>.</p>
|
||||
<p><b>About the <i>ADP® National Employment Report™<br></i></b>The ADP National Employment Report is an independent measure of the change in U.S. private employment and pay derived from actual, anonymized payroll data of client companies served by ADP, a leading provider of human capital management solutions. The report is produced by ADP Research in collaboration with the Stanford Digital Economy Lab.</p>
|
||||
<p>The ADP National Employment Report is broadly distributed to the public each month, free of charge, as part of the company's commitment to offering deeper insights of the U.S. labor market and providing businesses and governments with a source of credible and valuable information.</p>
|
||||
<p><b>About the ADP Research<br></b>The mission of ADP Research is to make the future of work more productive through data-driven discovery. Companies, workers, and policymakers rely on our finely tuned data and unique perspective to make informed decisions that impact workplaces around the world.</p>
|
||||
<p><b>About ADP (NASDAQ – ADP)<br></b>Designing better ways to work through cutting-edge products, premium services and exceptional experiences that enable people to reach their full potential. HR, Talent, Time Management, Benefits and Payroll. Informed by data and designed for people. Learn more at <a href="http://adp.com/" target="_blank" rel="nofollow">ADP.com</a></p>
|
||||
<p><i>ADP, the ADP logo, and Always Designing for People, </i><i>ADP National Employment Report, ADP Research Institute and ADP Research are trademarks of ADP, Inc. All other marks are the property of their respective owners. </i></p>
|
||||
<p><i>Copyright © 2025 ADP, Inc. All rights reserved.</i></p>
|
||||
<p><b>ADP-Media</b></p>
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder7559"><p><a href="https://mma.prnewswire.com/media/832721/ADP_Logo.html" target="_blank" rel="nofollow"><img src="https://mma.prnewswire.com/media/832721/ADP_Logo.jpg" title="(PRNewsfoto/ADP, LLC)" alt="(PRNewsfoto/ADP, LLC)"></a></p>
|
||||
</div><p> </p>
|
||||
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder0">
|
||||
<p>
|
||||
<a href="https://mma.prnewswire.com/media/2497479/ADP_Research_Logo.html" target="_blank" rel="nofollow">
|
||||
<img src="https://mma.prnewswire.com/media/2497479/ADP_Research_Logo.jpg" title="ADP Research (PRNewsfoto/ADP, Inc.)" alt="ADP Research (PRNewsfoto/ADP, Inc.)">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p>SOURCE ADP, Inc.</p></div>
|
||||
</body></html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<div class="wd_title wd_language_left">ADP National Employment Report: Private Sector Employment Shed 33,000 Jobs in June; Annual Pay was Up 4.4%</div>
|
||||
<div class="wd_date">Jul 2, 2025</div>
|
||||
<div class="wd_body wd_news_body fr-view" wd_resize="formatNews" wd_print_url="https://mediacenter.adp.com/2025-07-02-ADP-National-Employment-Report-Private-Sector-Employment-Shed-33,000-Jobs-in-June-Annual-Pay-was-Up-4-4?printable=1">
|
||||
<p><span class="legendSpanClass"><span class="xn-location">ROSELAND, N.J.</span></span>, <span class="legendSpanClass"><span class="xn-chron">July 2, 2025</span></span> /<a href="http://www.prnewswire.com/" target="_blank">PRNewswire</a>/ -- Private sector employment shed 33,000 jobs in June and annual pay was up 4.4 percent year-over-year, according to the June <a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow"><i>ADP National Employment Report</i><sup>®</sup></a> produced by ADP Research in collaboration with the Stanford Digital Economy Lab ("<span class="xn-person">Stanford Lab</span>"). The ADP National Employment Report is an independent measure and high-frequency view of the private-sector labor market based on actual, anonymized payroll data of more than 25 million U.S. employees.</p>
|
||||
<p>The jobs report and pay insights use ADP's fine-grained anonymized and aggregated payroll data to provide a representative picture of the private-sector labor market. The report details the current month's total private employment change, and weekly job data from the previous month. Because the underlying ADP payroll databases are continuously updated, the report provides a high-frequency, near real-time measure of U.S. employment. This measure reflects the number of employees on ADP client payrolls (Payroll Employment) to provide a richer understanding of the labor market. As of <span class="xn-chron">January 2025</span>, ADP's Pay Insights measure captures nearly 14.8 million individual pay change observations each month, up from nearly 10 million when it launched.</p>
|
||||
<p>"Though layoffs continue to be rare, a hesitancy to hire and a reluctance to replace departing workers led to job losses last month," said Dr. <span class="xn-person">Nela Richardson</span>, chief economist, ADP. "Still, the slowdown in hiring has yet to disrupt pay growth."</p>
|
||||
<p><b><u><span class="xn-chron">June 2025</span> Report Highlights</u></b><b>*</b></p>
|
||||
<p>View the <i>ADP National Employment Report</i> and interactive charts at <a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">www.adpemploymentreport.com</a>.</p>
|
||||
<p><b><u>JOBS REPORT</u></b></p>
|
||||
<p><b>Private employers shed 33,000 jobs in June<br></b>Job losses in professional and business services, and education and health services led the decline. Leisure and hospitality, and manufacturing showed gains.</p>
|
||||
<p><b><u>Change in U.S. Private Employment</u></b><b>: -33,000</b></p>
|
||||
<p><b>Change by Industry Sector<br></b>- <b>Goods-producing:</b> <b>32,000</b></p>
|
||||
<ul type="disc"><li>Natural resources/mining 8,000</li><li>Construction 9,000</li><li>Manufacturing 15,000</li></ul><p>- <b>Service-providing:</b> <b>-66,000</b></p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 14,000</li><li>Information 5,000</li><li>Financial activities -14,000</li><li>Professional/business services -56,000</li><li>Education/health services -52,000</li><li>Leisure/hospitality 32,000</li><li>Other services 5,000</li></ul><p><b>Change by U.S. Regions <br></b>- <b>Northeast: -3,000</b></p>
|
||||
<ul type="disc"><li>New England -10,000</li><li>Middle Atlantic 7,000</li></ul><p><b>- Midwest: -24,000</b></p>
|
||||
<ul type="disc"><li>East North Central 4,000</li><li>West North Central -28,000</li></ul><p>- <b>South: 13,000</b></p>
|
||||
<ul type="disc"><li>South Atlantic -21,000</li><li>East South Central 20,000</li><li>West South Central 14,000</li></ul><p>- <b>West: -20,000</b></p>
|
||||
<ul type="disc"><li>Mountain -20,000</li><li>Pacific 0</li></ul><p><b>Change by Establishment Size <br></b>- <b>Small establishments: -47,000</b></p>
|
||||
<ul type="disc"><li>1-19 employees -29,000</li><li>20-49 employees -18,000</li></ul><p>- <b>Medium establishments: -15,000</b></p>
|
||||
<ul type="disc"><li>50-249 employees 12,000</li><li>250-499 employees -27,000</li></ul><p><b>- Large establishments: 30,000</b></p>
|
||||
<ul type="disc"><li>500+ employees 30,000</li></ul><p><b><u>PAY INSIGHTS </u></b></p>
|
||||
<p><b>Pay gains held steady in June<br></b>Year-over-year pay growth for job-stayers was little changed for June at 4.4 percent compared to 4.5 percent in May. Pay growth for job-changers was 6.8 percent in June, down slightly from 7.0 percent last month.</p>
|
||||
<p><b><u>Median Change in Annual Pay (<i>ADP matched person sample</i>)<br></u></b><b>- Job-Stayers </b><b>4.4%<br></b>- <b>Job-Changers 6.8</b><b>%</b></p>
|
||||
<p><b>Median Change in Annual Pay for Job-Stayers by Industry Sector<br></b>- <b>Goods-producing:</b></p>
|
||||
<ul type="disc"><li>Natural resources/mining 4.5%</li><li>Construction 4.6%</li><li>Manufacturing 4.6%</li></ul><p>- <b>Service-providing:</b></p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 4.2%</li><li>Information 4.1%</li><li>Financial activities 5.2%</li><li>Professional/business services 4.2%</li><li>Education/health services 4.6%</li><li>Leisure/hospitality 4.7%</li><li>Other services 4.2%</li></ul><p><b>Median Change in Annual Pay for Job-Stayers by Firm Size <br></b>- <b>Small firms:</b></p>
|
||||
<ul type="disc"><li>1-19 employees 2.7%</li><li>20-49 employees 4.1%</li></ul><p><b>- Medium firms: </b></p>
|
||||
<ul type="disc"><li>50-249 employees 4.7%</li><li>250-499 employees 4.8%</li></ul><p><b>- Large firms: </b></p>
|
||||
<ul type="disc"><li>500+ employees 4.8%</li></ul><p><b>To see Pay Insights by U.S. State, Gender, and Age for Job-Stayers, visit </b><a href="https://payinsights.adp.com/" target="_blank" rel="nofollow"><b>here</b></a><b>.</b></p>
|
||||
<p>* Sum of components may not equal total due to rounding.</p>
|
||||
<p>The May total number of jobs added was revised from 37,000 to 29,000. The historical data file and weekly data for the previous month are available at <a href="https://adpemploymentreport.com/" target="_blank" rel="nofollow">https://adpemploymentreport.com/</a>.</p>
|
||||
<p>To subscribe to monthly email alerts or obtain additional information about the ADP National Employment Report, including employment and pay data, interactive charts, methodology, and a calendar of release dates, please visit <a href="https://adpemploymentreport.com/" target="_blank" rel="nofollow">https://adpemploymentreport.com/</a>. </p>
|
||||
<p>The <span class="xn-chron">July 2025</span> <i>ADP National Employment Report </i>will be released at <span class="xn-chron">8:15 a.m. ET</span> on <span class="xn-chron">July 30, 2025</span>.</p>
|
||||
<p><b>About the <i>ADP National Employment Report</i><sup>®<br></sup></b>The ADP National Employment Report is an independent measure of the change in U.S. private employment and pay derived from actual, anonymized payroll data of client companies served by ADP, a leading provider of human capital management solutions. The report is produced by ADP Research in collaboration with the Stanford Digital Economy Lab.</p>
|
||||
<p>The ADP National Employment Report is broadly distributed to the public each month, free of charge, as part of the company's commitment to offering deeper insights of the U.S. labor market and providing businesses and governments with a source of credible and valuable information.</p>
|
||||
<p><b>About the ADP Research<br></b>The mission of ADP Research is to make the future of work more productive through data-driven discovery. Companies, workers, and policymakers rely on our finely tuned data and unique perspective to make informed decisions that impact workplaces around the world.</p>
|
||||
<p><b>About ADP (NASDAQ – ADP)<br></b>Designing better ways to work through cutting-edge products, premium services and exceptional experiences that enable people to reach their full potential. HR, Talent, Time Management, Benefits and Payroll. Informed by data and designed for people. Learn more at <a href="http://adp.com/" target="_blank" rel="nofollow">ADP.com</a></p>
|
||||
<p><i>ADP, the ADP logo, and Always Designing for People, </i><i>ADP National Employment Report, and ADP Research are trademarks of ADP, Inc. All other marks are the property of their respective owners. </i></p>
|
||||
<p><i>Copyright © 2025 ADP, Inc. All rights reserved.</i></p>
|
||||
<p><b>ADP-Media</b></p>
|
||||
<p> </p>
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder5026"><p><a href="https://mma.prnewswire.com/media/832721/ADP_Logo.html" target="_blank" rel="nofollow"><img src="https://mma.prnewswire.com/media/832721/ADP_Logo.jpg" title="(PRNewsfoto/ADP, LLC)" alt="(PRNewsfoto/ADP, LLC)"></a></p>
|
||||
</div><p> </p>
|
||||
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder0">
|
||||
<p>
|
||||
<a href="https://mma.prnewswire.com/media/1212743/ADP_Research_Institute__Logov3.html" target="_blank" rel="nofollow">
|
||||
<img src="https://mma.prnewswire.com/media/1212743/ADP_Research_Institute__Logov3.jpg" title="ADP Research Institute, logo (PRNewsfoto/ADP, Inc.)" alt="ADP Research Institute, logo (PRNewsfoto/ADP, Inc.)">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p>SOURCE ADP, Inc.</p></div>
|
||||
</body></html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<div class="wd_title wd_language_left">ADP National Employment Report: Private Sector Employment Shed 32,000 Jobs in November; Annual Pay was Up 4.4%</div>
|
||||
<div class="wd_date">Dec 3, 2025</div>
|
||||
<div class="wd_body wd_news_body fr-view" wd_resize="formatNews" wd_print_url="https://mediacenter.adp.com/2025-12-03-ADP-National-Employment-Report-Private-Sector-Employment-Shed-32,000-Jobs-in-November-Annual-Pay-was-Up-4-4?printable=1">
|
||||
<p><span class="legendSpanClass"><span class="xn-location">ROSELAND, N.J.</span></span>, <span class="legendSpanClass">Dec. 3, 2025</span> /<a href="http://www.prnewswire.com/" target="_blank">PRNewswire</a>/ -- Private sector employment shed 32,000 jobs in November and pay was up 4.4 percent year-over-year according to the November <i><a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">ADP National Employment Report®</a></i> produced by ADP Research in collaboration with the Stanford Digital Economy Lab ("<span class="xn-person">Stanford Lab</span>"). </p>
|
||||
<p>The ADP National Employment Report is an independent measure of the labor market based on the anonymized weekly payroll data of more than 26 million private-sector employees in <span class="xn-location">the United States</span>. ADP's Pay Insights captures over 15 million individual pay change observations each month. Together, the jobs report and pay insights use ADP's fine-grained data to provide a representative and high-frequency picture of the private-sector labor market.</p>
|
||||
<p>"Hiring has been choppy of late as employers weather cautious consumers and an uncertain macroeconomic environment," said Dr. <span class="xn-person">Nela Richardson</span>, chief economist, ADP. "And while November's slowdown was broad-based, it was led by a pullback among small businesses."</p>
|
||||
<p><b><u><span class="xn-chron">November 2025</span> Report Highlights</u></b></p>
|
||||
<p>View the <i>ADP National Employment Report</i> and interactive charts at <a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">www.adpemploymentreport.com</a>.</p>
|
||||
<p><b><u>JOBS REPORT</u></b></p>
|
||||
<p><b>Private employers shed 32,000 jobs in November<br></b>Job creation has been flat during the second half of 2025 and pay growth has been on a downward trend. November hiring was particularly weak in manufacturing, professional and business services, information, and construction.</p>
|
||||
<p><b><u>Change in U.S. Private Employment</u></b><b>: -32,000</b></p>
|
||||
<p><b>Change by Industry</b></p>
|
||||
<p><b>- Goods-producing:</b> <b>-1</b><b>9,000</b></p>
|
||||
<ul type="disc"><li>Natural resources/mining 8,000</li><li>Construction -9,000</li><li>Manufacturing -18,000</li></ul><p><b>- Service-providing:</b> <b>-13,000</b></p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 1,000</li><li>Information -20,000</li><li>Financial activities -9,000</li><li>Professional/business services -26,000</li><li>Education/health services 33,000</li><li>Leisure/hospitality 13,000</li><li>Other services -4,000</li></ul><p><b>Change by U.S. Regions </b></p>
|
||||
<p><b>- Northeast: -100,000</b></p>
|
||||
<ul type="disc"><li>New England -50,000</li><li>Mid-Atlantic -49,000</li></ul><p><b>- Midwest: 45,000</b></p>
|
||||
<ul type="disc"><li>East North Central 41,000</li><li>West North Central 4,000</li></ul><p><b>- South: -43,000</b></p>
|
||||
<ul type="disc"><li>South Atlantic -78,000</li><li>East South Central 31,000</li><li>West South Central 3,000</li></ul><p><b>- West: 67,000</b></p>
|
||||
<ul type="disc"><li>Mountain 13,000</li><li>Pacific 54,000</li></ul><p><b>Change by Establishment Size </b></p>
|
||||
<p><b>- Small establishments: -120,000</b></p>
|
||||
<ul type="disc"><li>1-19 employees -46,000</li><li>20-49 employees -74,000</li></ul><p><b>- Medium establishments: 51,000</b></p>
|
||||
<ul type="disc"><li>50-249 employees 31,000</li><li>250-499 employees 20,000</li></ul><p><b>- Large establishments: 39,000</b></p>
|
||||
<ul type="disc"><li>500+ employees 39,000</li></ul><p><b><u>PAY INSIGHTS</u></b></p>
|
||||
<p><b>Pay growth slowed in November<br></b>Year-over-year pay for job-stayers rose 4.4 percent, down from 4.5 percent growth in October. For job-changers, pay was up 6.3 percent, slowing from 6.7 percent growth the month prior.</p>
|
||||
<p><b><u>Median Change in Annual Pay </u></b></p>
|
||||
<p><b>- Job-stayers </b><b>4.4%<br></b><b>- Job-changers </b><b>6.3%</b></p>
|
||||
<p><b><u>Median Change in Annual Pay for Job-Stayers by Industry </u></b></p>
|
||||
<p><b>- Goods-producing:</b> </p>
|
||||
<ul type="disc"><li>Natural resources/mining 4.3%</li><li>Construction 4.4%</li><li>Manufacturing 4.8%</li></ul><p><b>- Service-providing:</b> </p>
|
||||
<ul type="disc"><li>Trade/transportation/utilities 4.4%</li><li>Information 4.2%</li><li>Financial activities 5.2%</li><li>Professional/business services 4.2%</li><li>Education/health services 4.3%</li><li>Leisure/hospitality 4.5%</li><li>Other services 4.0%</li></ul><p><b>Median Change in Annual Pay for Job-Stayers by Firm Size</b></p>
|
||||
<p><b>- Small firms: </b></p>
|
||||
<ul type="disc"><li>1-19 employees 2.5%</li><li>20-49 employees 4.0%</li></ul><p><b>- Medium firms: </b></p>
|
||||
<ul type="disc"><li>50-249 employees 4.7%</li><li>250-499 employees 4.8%</li></ul><p><b>- Large firms: </b></p>
|
||||
<ul type="disc"><li>500+ employees 4.9%</li></ul><p><b>To see Pay Insights by U.S. State, Gender, and Age for Job-Stayers, visit <a href="https://payinsights.adp.com/" target="_blank" rel="nofollow">here</a>: </b></p>
|
||||
<p>The October total number of jobs added was revised from 42,000 to 47,000.</p>
|
||||
<p>For additional information about the ADP National Employment Report, including historical files, employment and pay data, methodology, and a calendar of release dates, please visit <a href="https://adpemploymentreport.com/" target="_blank" rel="nofollow">https://adpemploymentreport.com/</a>. </p>
|
||||
<p>The <span class="xn-chron">December 2025</span> <i>ADP National Employment Report </i>will be released on <span class="xn-chron">January 7, 2025</span> at <span class="xn-chron">8:15 a.m. ET</span>.</p>
|
||||
<p><b>About ADP Research <br></b>The mission of ADP Research is to make the future of work more productive through data-driven discovery. Companies, workers, and policy makers rely on our finely tuned data and unique perspective to make informed decisions that impact workplaces around the world.</p>
|
||||
<p>To subscribe to monthly email alerts or obtain additional information about ADP Research, including employment and pay data, methodology, and a calendar of release dates, please visit <a href="https://www.adpresearch.com" rel="nofollow">https://www.adpresearch.com</a>. </p>
|
||||
<p><b>About ADP (NASDAQ: ADP)<br></b>ADP has been shaping the world of work with innovation and expertise for more than 75 years. As a global leader in HR and payroll solutions, ADP continuously works to solve business challenges for our clients and their workers, from simple, easy-to-use tools for small businesses to fully integrated platforms for global enterprises – and everything in between. Always Designing for People means we're focused on just that – people. We use our unmatched AI-driven insights and proven expertise to design innovative solutions that help people achieve greater success at work. More than 1.1 million clients across 140+ countries rely on ADP's exceptional service to support their people and drive their business forward. HR, Talent, Time Management, Benefits, Compliance, and Payroll. Learn more at <a href="https://adp.com/" target="_blank" rel="nofollow">ADP.com</a></p>
|
||||
<p><i>ADP, the ADP logo, and Always Designing for People, </i><i>ADP National Employment Report, and ADP Research are registered trademarks of ADP, Inc. All other marks are the property of their respective owners. </i></p>
|
||||
<p><i>Copyright © 2025 ADP, Inc. All rights reserved.</i></p>
|
||||
<p><b>ADP-Media</b></p>
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder3885"><p><a href="https://mma.prnewswire.com/media/832721/ADP_Logo.html" target="_blank" rel="nofollow"><img src="https://mma.prnewswire.com/media/832721/ADP_Logo.jpg" title="(PRNewsfoto/ADP, LLC)" alt="(PRNewsfoto/ADP, LLC)"></a></p>
|
||||
</div><p> </p>
|
||||
|
||||
<div class="PRN_ImbeddedAssetReference" id="DivAssetPlaceHolder0">
|
||||
<p>
|
||||
<a href="https://mma.prnewswire.com/media/2497479/ADP_Research_Logo.html" target="_blank" rel="nofollow">
|
||||
<img src="https://mma.prnewswire.com/media/2497479/ADP_Research_Logo.jpg" title="ADP Research (PRNewsfoto/ADP, Inc.)" alt="ADP Research (PRNewsfoto/ADP, Inc.)">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<p>SOURCE ADP, Inc.</p></div>
|
||||
</body></html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<div class="wd_title wd_language_left">ADP National Employment Report: Private-Sector Employment Increased by 90,000 Jobs in September</div>
|
||||
<div class="wd_date">Sep 30, 2026</div>
|
||||
<div class="wd_body wd_news_body fr-view" wd_resize="formatNews" wd_print_url="https://mediacenter.adp.com/2026-09-30-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-90,000-Jobs-in-September?printable=1">
|
||||
<p class="prntac"><i>Base pay rose 3.2%; gross pay was up 4.7% </i></p><p><span class="legendSpanClass">ROSELAND, N.J.</span>, <span class="legendSpanClass">Sept. 30, 2026</span> /<a href="http://www.prnewswire.com/" target="_blank">PRNewswire</a>/ -- Private-sector employment increased by 90,000 jobs in September, according to the <a href="http://www.adpemploymentreport.com/?utm_source=PRnewsroom&utm_medium=pressrelease&utm_campaign=adpnerrelease&utm_content=prrelease020426link1" target="_blank" rel="nofollow"><i>ADP National Employment Report®</i></a> produced by ADP Research in collaboration with the Stanford Digital Economy Lab ("Stanford Lab").</p><p>Base pay for private-sector workers rose 3.2% and gross pay was up 4.7% year over year, according to <a href="https://payinsights.adp.com/" target="_blank" rel="nofollow">ADP Pay Insights.</a></p><ul type="disc"><li>Base pay for job-stayers rose 3.0% year-over-year; base pay for job-changers increased 4.8%.</li><li>Gross pay for job-stayers rose 4.4% year-over year; gross pay for job-changers increased 7.3%.</li></ul><p>The ADP National Employment Report is an independent measure of the labor market based on the anonymized weekly payroll data of more than 26 million private-sector employees in the United States. ADP Pay Insights uses payroll transaction data to compare the wage growth of individual workers over 12-month intervals, resulting in more than 14.7 million year-over-year pay-change observations each month.</p><p>Together, these reports use ADP's finely-grained data to provide a representative and high-frequency picture of the private-sector labor market.</p><p>"It's a strong report," said Dr. Nela Richardson, chief economist, ADP. "After a three-month slowdown, job creation rebounded and pay growth remained solid."</p><p><b><u>September 2026 Report Highlights</u></b></p><p>View the <i>ADP National Employment Report</i> and interactive charts at <a href="http://www.adpemploymentreport.com/" target="_blank" rel="nofollow">www.adpemploymentreport.com</a>.</p><p><b><u>EMPLOYMENT REPORT</u></b></p><p><b>Private employers added 90,000 jobs in September <br></b>Hiring accelerated for the first time since May, led by education and health care and leisure and hospitality. Financial activities and professional and business services showed weakness.</p><p><b><u>Change in U.S. Private Employment</u></b><b>:</b> <b>90,000</b></p><p><b>Change by Sector</b></p><p><b>- Goods-producing:</b> <b>31,000</b></p><ul type="disc"><li>Natural resources and mining -1,000</li><li>Construction 15,000</li><li>Manufacturing 17,000</li></ul><p><b>- Service-providing:</b> <b>59,000</b></p><ul type="disc"><li>Trade, transportation, and utilities 0</li><li>Information 3,000</li><li>Financial activities -16,000</li><li>Professional and business services -11,000</li><li>Education and health services 55,000</li><li>Leisure and hospitality 22,000</li><li>Other services 6,000</li></ul><p><b>Change by U.S. Regions </b></p><p><b>- Northeast:</b> <b>56,000</b></p><ul type="disc"><li>New England 9,000</li><li>Mid-Atlantic 47,000</li></ul><p><b>- Midwest:</b> <b>5,000</b></p><ul type="disc"><li>East North Central -10,000</li><li>West North Central 15,000</li></ul><p><b>- South:</b> <b>11,000</b></p><ul type="disc"><li>South Atlantic -10,000</li><li>East South Central -1,000</li><li>West South Central 22,000</li></ul><p><b>- West:</b> <b>17,000</b></p><ul type="disc"><li>Mountain 18,000</li><li>Pacific -1,000</li></ul><p><b>Change by Establishment Size </b></p><p><b>- Small</b> <b>establishments</b><b>:</b> <b>23,000</b></p><ul type="disc"><li>1-19 employees 18,000</li><li>20-49 employees 5,000</li></ul><p><b>- Medium establishments:</b> <b>54,000</b></p><ul type="disc"><li>50-249 employees 18,000</li><li>250-499 employees 36,000</li></ul><p><b>- Large establishments:</b> <b>14,000</b></p><ul type="disc"><li>500+ employees 14,000</li></ul><p><b><u>PAY INSIGHTS<br></u></b>ADP Pay Insights provides base pay growth and gross pay growth data across worker mobility, demographics, sector, employer size, pay quartile, and 56 U.S. metropolitan areas. The report also now offers pay growth distribution and an <a href="https://payinsights.adp.com/" target="_blank" rel="nofollow">interactive data platform</a>.</p><p><b>Base pay growth was little changed in September<br></b>Pay rose 3.0 percent for job-stayers year over year, and 4.8 percent for job-changers.</p><p>Visit our <a href="https://payinsights.adp.com/" target="_blank" rel="nofollow">interactive platform</a> for more information.</p><p><b><u>Median Change in Base Pay </u></b></p><ul type="disc"><li><b>All workers</b> <b>3.2%</b></li><li><b>Job-stayers</b> <b>3.0%</b></li><li><bLine truncated
|
||||
</body></html>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<!doctype html>
|
||||
<html><body>
|
||||
<ul class="wd_layout-simple wd_item_list"><li class="wd_item"><div class="wd_date">Oct 20, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-10-20-ADP-National-Employment-Report-Preliminary-Estimate-for-October-3,-2026">ADP National Employment Report Preliminary Estimate for October 3, 2026</a></div>
|
||||
|
||||
<div class="wd_summary"><p>For the four weeks ending October 3, 2026, U.S. private employers added an average of 18,500 jobs per week, according to the NER Pulse, a weekly update of the monthly ADP National Employment...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Oct 6, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-10-06-ADP-Lyric-HCM-Is-Now-Accessible-to-Companies-across-Key-European-Markets">ADP Lyric HCM Is Now Accessible to Companies across Key European Markets</a></div>
|
||||
<div class="wd_subtitle">AI-enabled platform makes managing a global workforce simpler by bringing HR, payroll, and workforce management together with localized compliance support and workforce intelligence. The solution is now available to companies headquartered in France, the United Kingdom, the Netherlands, and Germany, as well as to subsidiaries in 140 countries and territories.</div>
|
||||
<div class="wd_summary"><p>As artificial intelligence, evolving regulations and global business expansion increase the complexity of managing work across borders, ADP today announced the availability of ADP Lyric HCM for companies headquartered in the United Kingdom, France, Germany and the Netherlands...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Sep 30, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-09-30-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-90,000-Jobs-in-September">ADP National Employment Report: Private-Sector Employment Increased by 90,000 Jobs in September</a></div>
|
||||
|
||||
<div class="wd_summary"><p>Private-sector employment increased by 90,000 jobs in September, according to the ADP National Employment Report® produced by ADP Research in collaboration with the Stanford Digital Economy Lab...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Sep 15, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-09-15-ADP-National-Employment-Report-Preliminary-Estimate-for-August-29,-2026">ADP National Employment Report Preliminary Estimate for August 29, 2026</a></div>
|
||||
|
||||
<div class="wd_summary"><p>For the four weeks ending August 29, 2026, U.S. private employers added an average of 16,250 jobs per week, according to the NER Pulse, a weekly update of the monthly ADP National Employment...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Sep 2, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-09-02-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-38,000-Jobs-in-August">ADP National Employment Report: Private-Sector Employment Increased by 38,000 Jobs in August</a></div>
|
||||
|
||||
<div class="wd_summary"><p>Private-sector employment increased by 38,000 jobs in August, according to the ADP National Employment Report® produced by ADP Research in collaboration with the Stanford Digital Economy Lab...</p></div>
|
||||
|
||||
<ul class="wd_layout-simple wd_asset_type_link_list"><li class="wd_asset_type_link"><a href="https://mediacenter.adp.com/2026-09-02-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-38,000-Jobs-in-August#assets_20295_123604-117">
|
||||
<span class="wd_icon fa fa-photo" title="Photos" style="font-size: 1.2em;"></span>
|
||||
<span class="wd_title">Photos</span>
|
||||
<span class="wd_count">1</span>
|
||||
</a>
|
||||
</li></ul>
|
||||
</li><li class="wd_item"><div class="wd_date">Aug 5, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-08-05-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-44,000-Jobs-in-July-Annual-Pay-was-Up-4-4">ADP National Employment Report: Private Sector Employment Increased by 44,000 Jobs in July; Annual Pay was Up 4.4%</a></div>
|
||||
|
||||
<div class="wd_summary"><p>Private sector employment increased by 44,000 jobs in July and pay was up 4.4 percent year-over-year according to the July ADP National Employment Report® produced by ADP Research in...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Jul 1, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-07-01-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-98,000-Jobs-in-June-Annual-Pay-was-Up-4-4">ADP National Employment Report: Private Sector Employment Increased by 98,000 Jobs in June; Annual Pay was Up 4.4%</a></div>
|
||||
|
||||
<div class="wd_summary"><p>Private sector employment increased by 98,000 jobs in June and pay was up 4.4 percent year-over-year according to the June ADP National Employment Report® produced by ADP Research in...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Jun 23, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-06-23-ADP-National-Employment-Report-Preliminary-Estimate-for-June-6,-2026">ADP National Employment Report Preliminary Estimate for June 6, 2026</a></div>
|
||||
|
||||
<div class="wd_summary"><p>For the four weeks ending June 6, 2026, U.S. private employers added an average of 30,750 jobs per week, according to the NER Pulse, a weekly update of the monthly ADP National Employment Report...</p></div>
|
||||
|
||||
|
||||
</li><li class="wd_item"><div class="wd_date">Jun 3, 2026</div>
|
||||
<div class="wd_title"><a href="https://mediacenter.adp.com/2026-06-03-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-122,000-Jobs-in-May-Annual-Pay-was-Up-4-4">ADP National Employment Report: Private Sector Employment Increased by 122,000 Jobs in May; Annual Pay was Up 4.4%</a></div>
|
||||
|
||||
<div class="wd_summary"><p>Private sector employment increased by 122,000 jobs in May and pay was up 4.4 percent year-over-year according to the May ADP National Employment Report® produced by ADP Research in collaboration...</p></div>
|
||||
|
||||
|
||||
</li></ul>
|
||||
<ul class="wd_page_links"><li class="wd_page_link wd_page_prev"><span>«</span></li><li class="wd_page_link wd_page_active"><span aria-current="page" aria-label="Current page 1">1</span></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 2" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=9">2</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 3" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=18">3</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 4" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=27">4</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 5" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=36">5</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 6" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=45">6</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 7" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=54">7</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 8" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=63">8</a></li><li class="wd_page_link wd_page_mid2"><a aria-label="Show page 9" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=72">9</a></li><li class="wd_page_link wd_page_skip2"><span>…</span></li><li class="wd_page_link wd_page_end"><a aria-label="Show page 36" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=315">36</a></li><li class="wd_page_link wd_page_next"><a aria-label="Show next page" href="https://mediacenter.adp.com/press-releases?keywords=National+Employment+Report&o=9">»</a></li></ul>
|
||||
</body></html>
|
||||
@@ -0,0 +1,165 @@
|
||||
"""Verify monthly ADP identity, signed changes, revisions and publication matching."""
|
||||
import asyncio
|
||||
from datetime import UTC, date, datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from alphapulse.calendar import adp
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures/calendar"
|
||||
SEPTEMBER = (FIXTURES / "adp-2026-09.html").read_text()
|
||||
NEGATIVE = (FIXTURES / "adp-2025-06.html").read_text()
|
||||
CROSS_YEAR = (FIXTURES / "adp-2024-12.html").read_text()
|
||||
INDEX = (FIXTURES / "adp-index.html").read_text()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body,period,actual,previous,at", [
|
||||
(SEPTEMBER, "2026-09", "9", "3.6", datetime(2026, 9, 30, 12, 15, tzinfo=UTC)),
|
||||
(NEGATIVE, "2025-06", "-3.3", "2.9", datetime(2025, 7, 2, 12, 15, tzinfo=UTC)),
|
||||
(CROSS_YEAR, "2024-12", "12.2", None, datetime(2025, 1, 8, 13, 15, tzinfo=UTC)),
|
||||
])
|
||||
def test_official_monthly_readings_preserve_signs_revisions_and_eastern_time(body, period, actual, previous, at):
|
||||
report = adp.report(body)
|
||||
assert (report.period, report.at) == (period, at)
|
||||
assert report.values["adp_jobs"]["actual"] == actual
|
||||
assert report.values["adp_jobs"]["previous"] == previous
|
||||
|
||||
|
||||
def test_next_release_uses_explicit_dates_and_both_published_sentence_orders():
|
||||
next_month = adp.next_release(SEPTEMBER)
|
||||
assert next_month.period == "2026-10"
|
||||
assert next_month.at == datetime(2026, 11, 4, 13, 15, tzinfo=UTC)
|
||||
assert adp.next_release(NEGATIVE).at == datetime(2025, 7, 30, 12, 15, tzinfo=UTC)
|
||||
assert adp.next_release(CROSS_YEAR).period == "2025-01"
|
||||
events = adp.schedule(SEPTEMBER, "https://mediacenter.adp.com/report")
|
||||
assert events[0].external_id == "adp-2026-09"
|
||||
assert events[0].release_url == "https://mediacenter.adp.com/report"
|
||||
assert events[1].release_url is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", [
|
||||
"Access denied",
|
||||
SEPTEMBER.replace("Report Highlights", "Other period"),
|
||||
SEPTEMBER.replace("Change in U.S. Private Employment", "Other metric"),
|
||||
SEPTEMBER.replace("Sep 30, 2026", "Sep 30, 2025"),
|
||||
SEPTEMBER.replace("8:15 a.m.", "Time pending"),
|
||||
SEPTEMBER.replace("September 2026 Report Highlights", "August 2026 Report Highlights"),
|
||||
SEPTEMBER.replace("90,000 Jobs", "91,000 Jobs"),
|
||||
SEPTEMBER.replace("Private-Sector Employment Increased", "Preliminary Estimate Employment Increased"),
|
||||
], ids=["denied", "missing-period", "missing-reading", "wrong-year", "unknown-time", "wrong-month", "different-total", "weekly-report"])
|
||||
def test_changed_or_inconsistent_report_fails(body):
|
||||
with pytest.raises(ValueError):
|
||||
adp.report(body)
|
||||
|
||||
|
||||
def test_revisions_to_another_month_are_not_used_as_previous():
|
||||
body = SEPTEMBER.replace("The August total", "The July total")
|
||||
assert adp.report(body).values["adp_jobs"]["previous"] is None
|
||||
|
||||
|
||||
def test_invalid_future_schedule_does_not_discard_independent_current_readings():
|
||||
body = (FIXTURES / "adp-2025-11.html").read_text()
|
||||
assert adp.report(body).period == "2025-11"
|
||||
assert adp.report(body).values["adp_jobs"]["actual"] == "-3.2"
|
||||
with pytest.raises(ValueError, match="下次公布日期与统计月不符"):
|
||||
adp.next_release(body)
|
||||
with pytest.raises(ValueError, match="相邻统计月"):
|
||||
adp.next_release(SEPTEMBER.replace("The October 2026 ADP", "The November 2026 ADP"))
|
||||
|
||||
|
||||
def test_index_excludes_weekly_pulse_and_follows_actual_pagination():
|
||||
links, following = adp.release_links(INDEX, adp.INDEX, date(2025, 1, 1), date(2026, 10, 31))
|
||||
assert len(links) == 5
|
||||
assert all("Preliminary-Estimate" not in link for link in links)
|
||||
assert following.endswith("&o=9")
|
||||
links, following = adp.release_links(INDEX, adp.INDEX, date(2026, 10, 1), date(2026, 10, 31))
|
||||
assert links == []
|
||||
assert following is None
|
||||
links, following = adp.release_links(INDEX, adp.INDEX, date(2025, 1, 1), date(2025, 12, 31))
|
||||
assert links == []
|
||||
assert following is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", ["Access denied", INDEX.replace("https://mediacenter.adp.com/2026-09-30", "https://example.com/2026-09-30")])
|
||||
def test_invalid_index_or_external_report_link_fails(body):
|
||||
with pytest.raises(ValueError):
|
||||
adp.release_links(body, adp.INDEX, date(2025, 1, 1), date(2026, 10, 31))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("period,release_url,outcome", [("2026-09", "saved-report", "success"), ("2026-08", "saved-report", "error"),
|
||||
("2026-10", None, "pending"), ("2026-09", None, "success")])
|
||||
def test_worker_matches_actual_publication_and_statistical_month(trading, period, release_url, outcome):
|
||||
from alphapulse.calendar.worker import CalendarWorker
|
||||
|
||||
async def run():
|
||||
worker = CalendarWorker()
|
||||
try:
|
||||
async def fetch(source, url):
|
||||
text = INDEX if url == adp.INDEX else SEPTEMBER
|
||||
return httpx.Response(200, text=text, request=httpx.Request("GET", url if url.startswith("https") else "https://mediacenter.adp.com/report")), 1
|
||||
worker.fetch = fetch
|
||||
row = SimpleNamespace(source="adp", period=period, release_url=release_url,
|
||||
scheduled_at=datetime(2026, 11, 4, 13, 15, tzinfo=UTC) if period == "2026-10" else datetime(2026, 9, 30, 12, 15, tzinfo=UTC))
|
||||
if outcome == "error":
|
||||
with pytest.raises(ValueError, match="所选日程不符"):
|
||||
await worker.result_page(row)
|
||||
else:
|
||||
assert (await worker.result_page(row) is not None) == (outcome == "success")
|
||||
finally:
|
||||
await worker.http.aclose()
|
||||
await worker.bls.aclose()
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_history_preview_parses_stored_monthly_evidence_without_writes(trading, monkeypatch):
|
||||
from alphapulse.calendar import backfill
|
||||
|
||||
class Session:
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_):
|
||||
pass
|
||||
|
||||
async def execute(self, _):
|
||||
return SimpleNamespace(all=lambda: [])
|
||||
|
||||
async def scalars(self, _):
|
||||
return []
|
||||
|
||||
doc = backfill.Document("adp", "https://mediacenter.adp.com/2026-09-30-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-90,000-Jobs-in-September", SEPTEMBER.encode(), datetime(2026, 10, 31, tzinfo=UTC))
|
||||
monkeypatch.setattr(backfill, "SessionLocal", Session)
|
||||
monkeypatch.setattr(backfill, "fetch_documents", AsyncMock(return_value=[doc]))
|
||||
save = AsyncMock()
|
||||
monkeypatch.setattr(backfill, "save_announcement", save)
|
||||
result = asyncio.run(backfill.run(date(2026, 9, 30), date(2026, 9, 30), sources={"adp"}))
|
||||
assert result["announcements"] == {"adp_jobs": 1}
|
||||
assert result["errors"] == []
|
||||
assert result["mode"] == "dry-run"
|
||||
save.assert_not_awaited()
|
||||
|
||||
|
||||
def test_history_discovery_continues_past_newer_pages_without_matching_reports(trading, monkeypatch):
|
||||
from alphapulse.calendar import backfill
|
||||
|
||||
historical = '<ul class="wd_item_list"><li class="wd_item"><div class="wd_date">Jan 8, 2025</div><div class="wd_title"><a href="/2025-01-08-ADP-National-Employment-Report-Private-Sector-Employment-Increased-by-122,000-Jobs-in-December">ADP National Employment Report: Private Sector Employment Increased by 122,000 Jobs in December</a></div></li></ul>'
|
||||
calls = []
|
||||
|
||||
async def get(self, url, **kwargs):
|
||||
calls.append(url)
|
||||
body = CROSS_YEAR if "2025-01-08" in url else INDEX if url == adp.INDEX else historical
|
||||
return httpx.Response(200, text=body, request=httpx.Request("GET", url))
|
||||
|
||||
monkeypatch.setattr(httpx.AsyncClient, "get", get)
|
||||
monkeypatch.setattr(backfill, "ensure_quiet_window", AsyncMock())
|
||||
monkeypatch.setattr(backfill.asyncio, "sleep", AsyncMock())
|
||||
errors = []
|
||||
docs = asyncio.run(backfill.fetch_documents(date(2025, 1, 1), date(2025, 12, 31), set(), errors, sources={"adp"}))
|
||||
assert errors == []
|
||||
assert len(docs) == 1
|
||||
assert len(calls) == 3
|
||||
assert adp.report(docs[0].body.decode()).period == "2024-12"
|
||||
@@ -282,7 +282,7 @@ def test_worker_initializes_all_sources_and_drops_expired_month_status(trading):
|
||||
worker = CalendarWorker()
|
||||
worker.sources["https://www.federalreserve.gov/old-month.htm"] = {"error": "old"}
|
||||
async def unavailable(source, url, *, query=None):
|
||||
assert len(worker.sources) == 17
|
||||
assert len(worker.sources) == 18
|
||||
assert all(s["error"] for s in worker.sources.values())
|
||||
raise httpx.ConnectError("source unavailable")
|
||||
worker.fetch = unavailable
|
||||
|
||||
Reference in new issue
Block a user