브리핑의 가격 기준일과 중복 뉴스 처리
아침 브리핑을 만들 때 처음에는 자료를 모아 메시지를 보내는 데 집중했습니다. 그런데 매일 받아보니 숫자가 있다는 것과 오늘 참고할 수 있는 숫자라는 것은 달랐습니다.
국내 종목을 조회했을 때 일봉에는 며칠 전 값이 남아 있고, 분봉에서는 더 최근 정규장 값을 찾을 수 있는 경우가 있었습니다. 모델에 가격만 전달하면 어느 시점의 값인지 알 수 없습니다. 요약 문장을 고치기 전에 수집 결과에 날짜와 출처부터 붙여야 했습니다.
가격의 기준 날짜와 출처
일봉과 분봉 응답을 비교해 최근 정규장 값을 우선하도록 수정했습니다. 응답에서 확인할 수 없는 시간외 가격은 추정해서 채우지 않았습니다.
가격을 전달할 때는 비교 기준도 함께 넘겼습니다. 현재 값만 최근 자료로 바꾸고 이전 종가를 다른 날짜에서 가져오면 변동을 잘못 설명할 수 있기 때문입니다.
| 정보 | 확인할 내용 |
|---|---|
| 가격 기준일 | 어느 날의 값인지 |
| 이전 종가 기준일 | 어떤 거래일과 비교하는지 |
| 출처와 거래 구간 | 어디서 가져온 정규장·장외 값인지 |
| 자료 경과일 | 기준일로부터 얼마나 지났는지 |
개별주는 토스증권 조회를 먼저 시도하고, 지원하지 않거나 조회에 실패한 대상은 다른 출처를 사용하도록 정리했습니다. 조회 클라이언트 구현은 시세 조회 CLI 글에서 다룹니다.
날짜 정보를 추가한다고 매 항목에 긴 주석을 붙일 필요는 없었습니다. 공통 설명은 브리핑 앞부분에 모으고, 휴장이나 기준일 차이처럼 해당 항목에서 알아야 할 내용만 개별 표시했습니다.
가격 하나에 붙이는 최소 정보
문제를 재현할 때는 완성된 브리핑 전체보다 한 종목의 입력을 먼저 보는 편이 쉬웠습니다. 아래는 날짜와 거래 구간을 전달하는 방법을 설명하기 위한 가상 자료입니다. 특정 API의 실제 응답 스키마는 아닙니다.
{
"symbol": "EXAMPLE",
"price": 12000,
"currency": "KRW",
"price_date": "2026-10-16",
"session": "regular",
"previous_close": 11800,
"previous_close_date": "2026-10-15",
"fetched_at": "2026-10-18T08:00:00+09:00",
"source": "configured-market-provider"
}18일에 가져왔어도 가격이 16일 정규장 값일 수 있습니다. 주말이라면 이틀 전이라는 이유만으로 오류는 아닙니다. 그래서 가져온 시간과 거래 기준일을 분리하고, 최신 거래일인지 판단할 때는 해당 시장의 거래 일정을 함께 봐야 합니다.
등락률도 가격과 비교 종가의 조건이 맞을 때 계산합니다. 위 예시라면 (12000 - 11800) / 11800 * 100으로 약 1.69%입니다. 가격을 다른 출처로 대체하면서 비교 종가만 기존 응답에서 가져오면 통화·거래 구간·기준일이 어긋날 수 있습니다. 누락을 숫자 0으로 바꾸면 더 큰 오류가 됩니다. 가격을 확인하지 못한 상태와 실제 가격이 0인 상태는 의미가 다릅니다.
수집 결과를 확인할 때는 조회 성공 여부, 날짜, 통화, 비교 기준을 한 묶음으로 봅니다. 메시지에는 필요한 날짜 차이만 짧게 표시하되, 원본 기록에는 출처를 남겨 나중에 어느 응답에서 나온 숫자인지 찾을 수 있게 합니다.
뉴스 중복 방지
뉴스에서는 최근 브리핑에 사용한 링크를 기록하고 새 후보와 비교했습니다. 같은 기사에 추적용 쿼리만 달라진 경우를 줄이기 위해 주소를 정리한 뒤 비교했습니다. 한 피드의 기사만 브리핑을 채우지 않도록 출처별 수집 개수도 제한했습니다.
이 방식은 같은 링크가 반복되는 문제를 줄이는 데 목적이 있습니다. 여러 매체가 같은 사건을 각각 보도한 경우까지 링크 비교만으로 구분할 수 있는 것은 아닙니다.
메일에는 우선순위와 분류 이유를 붙였습니다. 어떤 메일을 먼저 읽을지 판단할 단서를 주되, 분류 결과만 보고 나머지를 확인하지 않아도 되는 자동화로 만들지는 않았습니다.
추적 쿼리를 제거한 링크 비교
주소를 정리할 때 모든 쿼리를 지우면 서로 다른 기사가 하나가 될 수 있습니다. id처럼 본문을 구분하는 값은 남기고, 알고 있는 추적용 항목만 제거하는 쪽으로 범위를 좁힙니다. 다음은 그 방식을 보여주는 예제입니다.
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
def news_key(url):
parts = urlsplit(url)
query = [(key, value) for key, value in parse_qsl(
parts.query, keep_blank_values=True
) if not key.lower().startswith("utm_")
and key.lower() not in {"fbclid", "gclid"}]
return urlunsplit((parts.scheme.lower(), parts.netloc.lower(),
parts.path, urlencode(sorted(query)), ""))이는 URL 기준의 중복 처리 예시입니다. 경로의 대소문자와 기사 식별 쿼리는 유지합니다. 정리한 키를 최근 발송 이력과 비교하면 같은 링크의 재등장을 줄일 수 있지만, 제목이 비슷한 별도 기사나 같은 사건의 후속 보도까지 제거하지는 않습니다. 사건 단위 중복과 기사 URL 중복을 같은 판단으로 묶지 않은 이유입니다.
인증 설정 통합
수집 대상이 늘면서 같은 인증값이 여러 수집기에 들어가 있는 문제도 보였습니다. 10월에 청약 조회를 확인하던 중 날씨와 대기 수집기에도 같은 공공데이터포털 인증값을 쓰고 있는 것을 찾았습니다.
이를 코드에 각각 두는 대신 공통 설정으로 읽도록 바꿨습니다. 환경변수를 먼저 확인하고, 값이 없으면 사용자 설정 파일을 읽습니다. 둘 다 없으면 인증 설정이 빠졌다는 오류를 남깁니다. 정식 브리핑에서 사용하지 않는 임시 청약 조회 코드도 정리했습니다.
설정 변경 후 실제 API 응답은 청약 조회에서 확인했습니다. 날씨와 대기 수집기는 같은 설정을 읽도록 수정한 범위입니다.
수집기에서 날짜나 인증 상태를 제대로 구분하지 않으면, 모델은 오래된 자료나 빠진 항목을 입력받은 채 그럴듯한 메시지를 만들 수 있습니다. 그래서 브리핑 문구뿐 아니라 요약에 넘기는 자료 자체를 먼저 정리했습니다. 최신 자료가 없다면 그 상태가 메시지에도 드러나도록 하는 편이 낫습니다.