DartBackfill.cs 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. namespace Application.Helpers;
  2. /// <summary>
  3. /// DART(OpenDART list.json) 공시 수집의 페이징/백필 판단 순수 로직 (DB/HTTP 비의존 — 테스트 가능).
  4. /// KRX 배치(날짜별 fetch)와 달리 DART 는 접수일 범위 페이징이라 별도 규칙이 필요하다:
  5. /// • 스펙 제한: corp_code 미지정 검색은 최근 3개월 접수분만 제공 → 요청 창을 3개월로 클램프.
  6. /// • 5만 행 상한: page_count=100 × MaxPages=500. 초기 백필처럼 창 전체가 미적재면 주 단위로 쪼개
  7. /// 창당 행 수를 낮춰 상한(정렬 desc 라 오래된 날짜부터 유실)을 회피한다.
  8. /// • 조기 종료: 일상 증분 수집에선 "한 페이지 전체가 기존 건"이면 그보다 오래된 페이지도 기수집으로
  9. /// 간주하고 종료 — 단 1페이지에서는 종료하지 않는다 (같은 날 재실행/부분 적재 방어 보수 조건).
  10. /// </summary>
  11. public static class DartBackfill
  12. {
  13. /// <summary>corp_code 미지정 list.json 의 스펙상 조회 한도 — 최근 3개월</summary>
  14. public const int SpecWindowMonths = 3;
  15. /// <summary>
  16. /// 요청 창 시작일을 스펙 한도(오늘-3개월)로 클램프. BackfillDays 가 90 초과로 설정돼도 요청 창이 3개월을 넘지 않는다.
  17. /// </summary>
  18. /// <returns>클램프된 시작일과 클램프 발생 여부 (발생 시 호출부가 경고 로그)</returns>
  19. public static (DateOnly Begin, bool Clamped) ClampToSpecWindow(DateOnly begin, DateOnly today)
  20. {
  21. var floor = today.AddMonths(-SpecWindowMonths);
  22. return begin < floor ? (floor, true) : (begin, false);
  23. }
  24. /// <summary>
  25. /// [begin, end] 포함 범위를 7일 단위 (bgn_de, end_de) 청크로 분할 — 오래된 쪽부터 순서대로.
  26. /// begin > end 면 빈 목록. 마지막 청크는 end 에서 잘린다.
  27. /// </summary>
  28. public static IReadOnlyList<(DateOnly Begin, DateOnly End)> SplitWeekly(DateOnly begin, DateOnly end)
  29. {
  30. var chunks = new List<(DateOnly Begin, DateOnly End)>();
  31. for (var chunkBegin = begin; chunkBegin <= end; chunkBegin = chunkBegin.AddDays(7))
  32. {
  33. var chunkEnd = chunkBegin.AddDays(6);
  34. if (chunkEnd > end)
  35. {
  36. chunkEnd = end;
  37. }
  38. chunks.Add((chunkBegin, chunkEnd));
  39. }
  40. return chunks;
  41. }
  42. /// <summary>
  43. /// 증분 수집 조기 종료 판정 — 해당 페이지의 모든 행이 이미 DB 에 존재하고(신규 0), 1페이지가 아닐 때만 true.
  44. /// 1페이지 제외 이유: 같은 날 재실행이면 1페이지도 전부 기존이지만, 과거에 부분 적재된 창일 가능성을
  45. /// 배제할 수 없어 최소 2페이지까지는 확인한다 (비용: 페이지 1콜).
  46. /// </summary>
  47. public static bool ShouldStopEarly(int pageNo, int rowCount, int newCount)
  48. {
  49. return pageNo > 1 && rowCount > 0 && newCount == 0;
  50. }
  51. }