using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Stocks; /// /// SEIBro per-키 rolling 수집의 "조회 시도" 마커 (자연키 UNIQUE (JobKey, EntityKey)) — SEIBro 통합 리뷰 결함 #1 해소용. /// 완료 판정을 "대상/자식 테이블에 행이 있으면 수집됨"으로 하면, 정상적으로 0행을 반환하는 API(getIntPayInfo·getCDInfo 등)의 키가 /// 매 실행 최우선 재조회되어 나머지 유니버스가 굶는다(starvation, resumable 붕괴). 이 마커는 조회를 시도할 때마다(0행이어도) /// LastCheckedAt 을 스탬핑하고, rolling 대상 선정을 "데이터 존재"가 아닌 "이 마커의 LastCheckedAt(없으면 최우선 → 오래된 순)"으로 /// 정렬하게 한다. 스키마 오염 없이 전 rolling job 에 일관 적용하려고 마스터/대상 엔티티가 아닌 공용 테이블로 둔다. /// JobKey = job 식별자(예 "bond-intpay", "bond-cd"), EntityKey = 대상 키 문자열(ISIN·회사번호 등, 복합키는 구분자로 결합). /// public class SeibroFetchMarker { [Key] public int ID { get; private set; } /// 수집 job 식별자 (예 "bond-intpay", "deriv-master", "corp-meeting") public string JobKey { get; private set; } = default!; /// 대상 키 (ISIN·발행회사번호 등, 복합키는 배치가 구분자로 결합해 전달) public string EntityKey { get; private set; } = default!; /// 마지막 조회 시도 시각 (UTC) — 0행이어도 갱신. rolling 대상 정렬 기준. public DateTime LastCheckedAt { get; private set; } = DateTime.UtcNow; private SeibroFetchMarker() { } public static SeibroFetchMarker Create(string jobKey, string entityKey) { if (string.IsNullOrWhiteSpace(jobKey)) { throw new ArgumentException("jobKey required", nameof(jobKey)); } if (string.IsNullOrWhiteSpace(entityKey)) { throw new ArgumentException("entityKey required", nameof(entityKey)); } return new SeibroFetchMarker { JobKey = jobKey.Trim(), EntityKey = entityKey.Trim(), LastCheckedAt = DateTime.UtcNow }; } /// 조회 시도 시각을 현재로 스탬핑 (0행 응답 포함, 매 조회마다 호출) public void Touch() { LastCheckedAt = DateTime.UtcNow; } }