Skip to content

[Feat] 셔틀 시간표 배치 수집 기반 구현 - #87

Merged
JangDongHo merged 7 commits into
devfrom
feat/data-scraper
Jun 15, 2026
Merged

JangDongHo merged 7 commits into
devfrom
feat/data-scraper

Conversation

@JangDongHo

@JangDongHo JangDongHo commented Jun 15, 2026 •

Copy link
Copy Markdown
Member

📌 개요

  • 교내 셔틀버스 시간표를 별도 batch 서비스에서 주기적으로 수집·파싱하기 위한 기반을 추가했습니다.
  • batch 작업을 도메인별 Job으로 분리하고, 셔틀 시간표 수집 흐름을 fetch → parse → repository 단계로 나눴습니다.
  • 실제 경상국립대학교 셔틀버스 HTML fixture를 기준으로 노선별 시간표와 최근 업데이트 일시를 검증할 수 있게 했습니다.

✨ 작업 내용

  • Nest 기반 batch 앱을 추가하고 스케줄러가 등록된 Batch Job들을 순차 실행하도록 구성했습니다.
  • Shuttle, Cafeteria, Notice, Academic Calendar Job 뼈대를 추가해 교내 데이터 수집 도메인을 분리했습니다.
  • 공통 FetchHttpClient를 추가해 네트워크 오류, timeout, 429, 5xx 응답에 대한 retry 정책을 제공했습니다.
  • ShuttleClient가 교내 셔틀버스 페이지 HTML을 공통 HTTP client로 가져오도록 구현했습니다.
  • ShuttleParser가 fixture HTML에서 2개 노선의 오전/오후 시간표와 최근 업데이트 일시를 파싱하도록 구현했습니다.
  • 파싱 결과를 저장 전 중간 데이터인 ParsedShuttleTimetablePage와 ParsedShuttleTimetable로 모델링했습니다.
  • 셔틀 시간표 TypeORM entity와 repository 초안을 추가해 shuttle_timetable 저장 흐름을 이어갈 수 있게 했습니다.
  • HTTP client, Shuttle client, Shuttle parser 단위 테스트를 추가했습니다.

🔥 변경 이유

  • API 서버가 최신 셔틀 시간표를 안정적으로 제공하려면 외부 교내 HTML을 직접 조회하는 책임을 API 요청 흐름에서 분리해야 합니다.
  • 셔틀, 학식, 공지, 학사일정은 수집 주기와 원본 포맷이 다르므로 batch Job 단위로 책임을 나누는 구조가 필요합니다.
  • 학교 페이지 HTML 구조가 바뀌거나 필수 데이터가 누락될 때 빈 데이터가 조용히 저장되지 않도록 fixture 기반 parser 테스트를 먼저 마련했습니다.
  • 네트워크 장애가 일시적일 수 있어 batch 수집 단계에서 공통 retry 정책을 재사용할 수 있게 했습니다.

🧪 테스트

  • 정상 동작 확인
  • 예외 케이스 확인
  • services/api/batch에서 pnpm test -- --runInBand
  • services/api/batch에서 pnpm build
  • HTTP client retry 케이스 검증
  • 셔틀 HTML fixture 기반 parser 검증

🔗 관련 이슈

Summary by CodeRabbit

새 기능

  • 배치 처리 시스템 도입: 애플리케이션 시작 시 및 매 정각마다 배치 작업이 자동으로 순차 실행됩니다.
  • 자동 데이터 동기화: 셔틀, 식당, 공지사항, 학사일정 등 주요 데이터의 자동 동기화를 지원합니다.
  • 안정적인 통신 기능: HTTP 장애 발생 시 설정된 횟수 내에서 자동으로 재시도하여 서비스 안정성을 향상시켰습니다.

* 지정된 URL에서 셔틀 시간표 HTML을 가져오는 ShuttleClient 추가
* 셔틀 데이터 수집과 파싱 흐름을 담당하는 ShuttleJob 구현
* 셔틀 시간표 데이터 파싱을 위한 ShuttleParser 기본 구조 추가
* 파싱된 셔틀 데이터를 저장하는 ShuttleRepository 추가
* ShuttleClient의 정상적인 HTML 요청 동작을 검증하는 단위 테스트 추가
* 비동기 작업 지연을 위한 delay 유틸 함수 추가
* Jest 테스트 타입을 사용할 수 있도록 TypeScript 설정 업데이트
* 데이터베이스 연결 설정을 위한 DatabaseModule 추가
* 셔틀 시간표 저장을 위한 ShuttleTimetable 엔티티 생성
* 기존 셔틀 인터페이스 제거 후 파싱된 시간표 타입으로 대체
* HTML 셔틀 시간표를 구조화된 데이터로 변환하는 ShuttleParser 구현
* ShuttleParser의 시간표 파싱 동작을 검증하는 단위 테스트 추가
* ShuttleRepository가 새로운 ShuttleTimetable 엔티티를 사용하도록 수정
@coderabbitai

coderabbitai Bot commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@JangDongHo, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 38 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 49322827-93dd-405b-a5ba-0155141b90e1

📥 Commits

Reviewing files that changed from the base of the PR and between e7e886f and d409159.

📒 Files selected for processing (7)
  • lint-staged.config.js
  • services/api/batch/src/batch/batch.service.ts
  • services/api/batch/src/batch/http/fetch-http.client.ts
  • services/api/batch/src/batch/jobs/shuttle/domain/shuttle-timetable.entity.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.job.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.parser.spec.ts
  • services/api/batch/src/main.ts

Walkthrough

services/api/batch 디렉터리를 신규 NestJS 배치 서비스로 구성한다. 재시도 가능한 FetchHttpClient, 셔틀 시간표 HTML 파서(ShuttleParser), ShuttleClient/ShuttleRepository, 그리고 BATCH_JOBS DI 토큰 기반 배치 잡 스케줄러를 포함하며, 프로젝트 설정 파일 일체와 단위 테스트를 함께 추가한다.

Changes

배치 서비스 초기 구현

Layer / File(s) Summary
프로젝트 설정 및 툴체인 구성
services/api/batch/package.json, services/api/batch/tsconfig.json, services/api/batch/tsconfig.build.json, services/api/batch/eslint.config.mjs, services/api/batch/.prettierrc, services/api/batch/nest-cli.json, services/api/batch/.gitignore, services/api/batch/README.md
NestJS 배치 서비스의 패키지 식별자·스크립트·의존성, TypeScript/ESLint/Prettier 구성, Nest CLI 설정, gitignore, README를 모두 신규 추가한다.
재시도 가능한 HTTP 클라이언트 구현
services/api/batch/src/batch/http/http-client.interface.ts, services/api/batch/src/batch/http/error/http-request.error.ts, services/api/batch/src/batch/utils/delay.ts, services/api/batch/src/batch/http/fetch-http.client.ts, services/api/batch/src/batch/http/fetch-http.client.spec.ts
RetryOptions/HttpRequestOptions/HttpClient 인터페이스와 HttpRequestError를 정의하고, AbortController 기반 타임아웃과 재시도 루프를 갖춘 FetchHttpClient를 구현한다. 성공(200), 네트워크 오류, 5xx·429 재시도, 404 미재시도 케이스를 단위 테스트로 검증한다.
셔틀 시간표 도메인 타입 및 엔티티
services/api/batch/src/batch/jobs/shuttle/type/parsed-shuttle-timetable.ts, services/api/batch/src/batch/jobs/shuttle/domain/shuttle-timetable.entity.ts
파싱 결과 타입(ShuttleTimetableMap, ParsedShuttleTimetable, ParsedShuttleTimetablePage)과 shuttle_timetable TypeORM 엔티티(id, routeName, timetable jsonb, updatedAt)를 정의한다.
셔틀 시간표 수집·파싱·저장 파이프라인
services/api/batch/src/batch/jobs/shuttle/shuttle.client.ts, services/api/batch/src/batch/jobs/shuttle/shuttle.client.spec.ts, services/api/batch/src/batch/jobs/shuttle/shuttle.parser.ts, services/api/batch/src/batch/jobs/shuttle/shuttle.parser.spec.ts, services/api/batch/src/batch/jobs/shuttle/shuttle.repository.ts, services/api/batch/src/batch/jobs/shuttle/shuttle.job.ts
ShuttleClient가 고정 URL에서 HTML을 가져오고, ShuttleParser가 테이블 2개·오전/오후 시간·최근 업데이트 일시를 파싱하며, ShuttleRepository(현재 더미 구현)에 저장하는 ShuttleJob을 구성한다. 클라이언트와 파서 모두 단위 테스트를 포함한다.
BatchJob DI 구조 및 스텁 잡 등록
services/api/batch/src/batch/jobs/batch-job.interface.ts, services/api/batch/src/batch/jobs/cafeteria/cafeteria.job.ts, services/api/batch/src/batch/jobs/notice/notice.job.ts, services/api/batch/src/batch/jobs/academic-calendar/academic-calendar.job.ts, services/api/batch/src/batch/batch.module.ts, services/api/batch/src/batch/database/database.module.ts
BATCH_JOBS 심볼·BatchJob 인터페이스를 정의하고, 빈 구현의 CafeteriaJob·NoticeJob·AcademicCalendarJob 스텁을 추가한다. BatchModule이 useFactory로 네 잡을 BatchJob[]으로 집계하며, DatabaseModule은 주석 처리된 상태로 포함된다.
BatchService 스케줄러 및 애플리케이션 부트스트랩
services/api/batch/src/batch/batch.service.ts, services/api/batch/src/main.ts
BatchService가 BATCH_JOBS를 순차 await로 실행하는 run()을 매 정각 cron과 부트스트랩 시 호출하며, main.ts가 BatchModule로 NestJS 앱을 초기화한다.

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler as Scheduler (Cron / Bootstrap)
  participant BatchService
  participant ShuttleJob
  participant ShuttleClient
  participant FetchHttpClient
  participant ShuttleParser

  Scheduler->>BatchService: run() 호출
  loop BATCH_JOBS 순차 실행
    BatchService->>ShuttleJob: job.run()
    ShuttleJob->>ShuttleClient: fetch()
    ShuttleClient->>FetchHttpClient: getText(SHUTTLE_URL)
    FetchHttpClient-->>ShuttleClient: HTML string (재시도 포함)
    ShuttleClient-->>ShuttleJob: HTML string
    ShuttleJob->>ShuttleParser: parse(raw)
    ShuttleParser-->>ShuttleJob: ParsedShuttleTimetablePage
    ShuttleJob-->>BatchService: 완료 (repository.save 주석 처리됨)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Infra

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항인 셔틀 시간표 배치 수집 기반 구현을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 제공된 템플릿 구조에 맞춰 개요, 작업 내용, 변경 이유, 테스트, 관련 이슈를 모두 포함하고 있습니다.
Linked Issues check ✅ Passed 모든 핵심 목표가 구현되었습니다: Batch Job 인터페이스 및 DI 구조, ShuttleJob/Client/Parser/Repository 책임 분리, 공통 FetchHttpClient 재시도 정책, fixture 기반 파싱, 중간 데이터 모델링, 단위 테스트 추가 등 issue #86의 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 issue #86의 범위 내입니다: 배치 기반 구조, HTTP 클라이언트, 셔틀 파서, 저장소, 관련 설정 파일과 테스트 추가만 포함되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/data-scraper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JangDongHo
JangDongHo marked this pull request as ready for review June 15, 2026 01:15
@JangDongHo JangDongHo self-assigned this Jun 15, 2026
@JangDongHo JangDongHo added Shuttle 셔틀버스 도메인 Feat Batch labels Jun 15, 2026
@JangDongHo
JangDongHo changed the base branch from main to dev June 15, 2026 01:19
@JangDongHo

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/api/batch/README.md (1)

1-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

README는 NestJS 보일러플레이트이며, 배치 서비스의 실제 기능을 설명하지 않음.

현재 README는 일반적인 NestJS 프로젝트 템플릿 복사본입니다. PR 목표에 따르면 이 배치 서비스는 다음을 구현합니다:

  • 셔틀 시간표 수집 및 파싱 파이프라인
  • 배치 잡 스케줄러 (cron 기반)
  • 도메인별 배치 작업 분리 (Shuttle, Cafeteria, Notice, Academic Calendar)
  • HTTP 재시도 클라이언트

README는 이러한 배치 서비스의 목적, 아키텍처, 로컬 개발 설정을 전혀 설명하지 않습니다. 또한 markdownlint 경고도 있습니다:

  • Lines 5-6: 사용되지 않은 참조 정의 제거 필요 (circleci-image, circleci-url)
  • Lines 31, 67-68: 명령 뒤에 출력 또는 컨텍스트 필요

배치 서비스에 맞는 README를 생성해드릴 수 있습니다. 다음 내용을 포함하는 README를 작성하시겠습니까?

  • 배치 서비스 목적 및 역할
  • 프로젝트 구조 (HTTP 클라이언트, 파서, 도메인별 잡)
  • 로컬 개발 및 실행 방법
  • 스케줄링 및 작업 흐름
  • 테스트 실행 방법
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/api/batch/README.md` around lines 1 - 99, Replace the generic NestJS
boilerplate README with documentation specific to the batch service. Remove the
unused reference definitions (circleci-image and circleci-url) and add context
or expected output after the command examples to fix markdownlint warnings. The
new README should document the batch service's actual purpose (shuttle timetable
collection and parsing, cron-based job scheduling, domain-separated batch jobs
for Shuttle, Cafeteria, Notice, and Academic Calendar, and HTTP retry client),
explain the project structure and architecture, provide local development and
execution setup instructions, describe the scheduling and job workflow, and
include test execution guidance.
🧹 Nitpick comments (2)
services/api/batch/src/batch/http/fetch-http.client.spec.ts (1)

4-79: ⚡ Quick win

타임아웃 재시도 경로 테스트를 추가해 회귀를 막아주세요.

PR 목표에 timeout 처리/재시도가 포함되어 있는데, 현재 스펙은 해당 경로를 직접 검증하지 않습니다. timeout 발생 후 재시도 성공(또는 최종 실패) 케이스 1개는 추가하는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/api/batch/src/batch/http/fetch-http.client.spec.ts` around lines 4 -
79, Add a new test case to the FetchHttpClient describe block that verifies
timeout retry behavior. Create a test that mocks the fetch function to throw a
timeout error (or abort error) on the first call and then resolves successfully
on retry, similar to the existing network error test. This test should validate
that the client properly retries when a timeout occurs and successfully returns
the response. Alternatively, you could add a test verifying that the client
exhausts retries and throws an error when timeouts persist across all retry
attempts.
services/api/batch/src/batch/jobs/shuttle/shuttle.client.spec.ts (1)

11-13: ⚡ Quick win

요청 URL 인자 검증을 추가하세요.

현재는 호출 횟수만 확인해서, 잘못된 URL로 바뀌어도 테스트가 통과합니다. getText 호출 인자까지 검증해 회귀를 막는 편이 안전합니다.

🔧 제안 diff
     await expect(client.fetch()).resolves.toBe('<html></html>');
     expect(httpClient.getText).toHaveBeenCalledTimes(1);
+    expect(httpClient.getText).toHaveBeenCalledWith(
+      'https://www.gnu.ac.kr/main/cm/cntnts/cntntsView.do?mi=1358&cntntsId=1194',
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.client.spec.ts` around
lines 11 - 13, The test for the shuttle client only verifies that
httpClient.getText is called once, but does not validate what URL argument is
passed to it. This means the test would pass even if the URL argument changes
incorrectly. Add argument validation to the mock expectation by using
toHaveBeenCalledWith() in addition to toHaveBeenCalledTimes(1), specifying the
expected URL that should be passed to the getText method to prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/api/batch/eslint.config.mjs`:
- Around line 1-35: The batch service path needs to be added to the lint-staged
configuration to ensure its TypeScript and JavaScript files are automatically
linted. Update lint-staged.config.js to include a new pattern entry for the
batch service path that calls projectCommands with the services/api/batch
directory and the matched files, following the same pattern used for other
service paths in the configuration.

In `@services/api/batch/src/batch/batch.service.ts`:
- Around line 14-16: The for loop iterating through this.jobs in the batch
service does not handle exceptions from individual job.run() calls, causing a
single failed job to halt the entire batch execution and skip subsequent jobs.
Wrap the await job.run() call inside a try-catch block to isolate failures at
the job level, log the error appropriately, and allow the loop to continue
processing remaining jobs in the this.jobs collection. This ensures fault
isolation within the batch structure and allows jobs to execute independently.
- Around line 12-20: The run() method scheduled by `@Cron` decorator and called
from onApplicationBootstrap() lacks a re-entrance guard, allowing concurrent
execution of the same jobs if a scheduled run takes longer than the cron
interval. Add a boolean flag (e.g., isRunning) to track whether a run is
currently in progress. At the start of the run() method, check if isRunning is
true and return early if so; otherwise, set isRunning to true, execute all jobs,
and finally set isRunning to false in a try-finally block to ensure the flag is
reset even if an error occurs.

In `@services/api/batch/src/batch/http/fetch-http.client.ts`:
- Around line 93-99: The getRetryOptions method lacks validation for the retry
configuration values, which can cause unstable behavior when invalid values like
negative retries or non-positive timeoutMs are passed. Add validation logic in
the getRetryOptions method to ensure retries is non-negative, timeoutMs is
positive (greater than zero), retryDelayMs is non-negative, and retryOnStatuses
is valid. Either throw an error for invalid inputs or enforce minimum/default
values to guarantee the returned configuration is always safe for use.
- Around line 50-55: The retry logic in the catch block does not distinguish
between abort errors (caused by the caller's signal being aborted) and other
retriable errors, which allows abort signals to be retried instead of
terminating immediately. Add a check in the catch block to detect if the error
is an AbortError or signal cancellation error (before calling shouldRetryError),
and if so, throw it immediately without retrying, bypassing the retry logic
regardless of the attempt count. This ensures that when a caller aborts the
signal, the operation terminates instantly without waiting for retries.

In
`@services/api/batch/src/batch/jobs/shuttle/domain/shuttle-timetable.entity.ts`:
- Around line 20-21: The updatedAt field in the ShuttleTimetableEntity is
decorated with `@UpdateDateColumn` which automatically sets it to the current save
time on every entity save, causing the loss of the original source update time
extracted by the shuttle parser. Change the `@UpdateDateColumn` decorator to
`@Column` to prevent automatic updates, and ensure that when the entity is created
or saved in shuttle.parser.ts or related save operations, the actual update time
extracted from the source is explicitly assigned to the updatedAt field. This
way the original page-based update time will be preserved instead of being
overwritten.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.job.ts`:
- Around line 17-22: The batch job's run() method is incomplete and does not
persist fetched shuttle data to the database. In the run() method of
shuttle.job.ts, uncomment and enable the this.repository.save() call that is
currently commented out, and ensure the parsed data from this.parser.parse() is
properly passed to the save method (check that the variable name matches what
the repository.save() method expects, likely shuttles). Additionally, implement
the save() method in shuttle.repository.ts with actual database persistence
logic (upsert or insert logic) rather than leaving it as a no-op, so that the
parsed shuttle schedule data is actually written to the database when the batch
job runs.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.parser.spec.ts`:
- Around line 39-42: The test case description in the shuttle parser spec
indicates it should test the scenario where "최근 업데이트 일시가 없으면" (no recent update
date/time exists), but the expected error message is about a missing timetable
table instead. Fix this mismatch by either providing test input HTML that
contains a timetable structure but lacks the update date/time information, and
then updating the expected error message to match the actual error thrown when
update date/time is missing; or alternatively, rename the test case description
to accurately reflect that it tests for a missing timetable scenario when
parsing empty HTML.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.parser.ts`:
- Around line 17-23: Introduce a custom ShuttleParseError exception class with
specific error codes (MISSING_UPDATED_AT and MISSING_TIMETABLE) to replace
generic Error strings and enable proper failure cause classification. In the
parse() method, reorganize validation checks to validate updatedAt extraction
before timetables validation, then throw the appropriate ShuttleParseError with
the correct code based on which validation fails. Update the timetable length
check around line 17-23 to throw ShuttleParseError with MISSING_TIMETABLE code,
and update the updatedAt validation around lines 109-116 to throw
ShuttleParseError with MISSING_UPDATED_AT code instead of generic Error
messages.

In `@services/api/batch/src/main.ts`:
- Line 8: The bootstrap() function call on line 8 does not explicitly handle
Promise rejections, which means initialization failures are silently ignored
unless caught by a global unhandled rejection handler. Add explicit error
handling by chaining a .catch() method to the bootstrap() call that logs the
error using an appropriate logger and then calls process.exit() with a non-zero
exit code to properly signal initialization failure to the system.

---

Outside diff comments:
In `@services/api/batch/README.md`:
- Around line 1-99: Replace the generic NestJS boilerplate README with
documentation specific to the batch service. Remove the unused reference
definitions (circleci-image and circleci-url) and add context or expected output
after the command examples to fix markdownlint warnings. The new README should
document the batch service's actual purpose (shuttle timetable collection and
parsing, cron-based job scheduling, domain-separated batch jobs for Shuttle,
Cafeteria, Notice, and Academic Calendar, and HTTP retry client), explain the
project structure and architecture, provide local development and execution
setup instructions, describe the scheduling and job workflow, and include test
execution guidance.

---

Nitpick comments:
In `@services/api/batch/src/batch/http/fetch-http.client.spec.ts`:
- Around line 4-79: Add a new test case to the FetchHttpClient describe block
that verifies timeout retry behavior. Create a test that mocks the fetch
function to throw a timeout error (or abort error) on the first call and then
resolves successfully on retry, similar to the existing network error test. This
test should validate that the client properly retries when a timeout occurs and
successfully returns the response. Alternatively, you could add a test verifying
that the client exhausts retries and throws an error when timeouts persist
across all retry attempts.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.client.spec.ts`:
- Around line 11-13: The test for the shuttle client only verifies that
httpClient.getText is called once, but does not validate what URL argument is
passed to it. This means the test would pass even if the URL argument changes
incorrectly. Add argument validation to the mock expectation by using
toHaveBeenCalledWith() in addition to toHaveBeenCalledTimes(1), specifying the
expected URL that should be passed to the getText method to prevent regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bf3064b0-595b-4496-86b4-09695ba33259

📥 Commits

Reviewing files that changed from the base of the PR and between 0232c29 and e7e886f.

⛔ Files ignored due to path filters (1)
  • services/api/batch/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • services/api/batch/.gitignore
  • services/api/batch/.prettierrc
  • services/api/batch/README.md
  • services/api/batch/eslint.config.mjs
  • services/api/batch/nest-cli.json
  • services/api/batch/package.json
  • services/api/batch/src/batch/batch.module.ts
  • services/api/batch/src/batch/batch.service.ts
  • services/api/batch/src/batch/database/database.module.ts
  • services/api/batch/src/batch/http/error/http-request.error.ts
  • services/api/batch/src/batch/http/fetch-http.client.spec.ts
  • services/api/batch/src/batch/http/fetch-http.client.ts
  • services/api/batch/src/batch/http/http-client.interface.ts
  • services/api/batch/src/batch/jobs/academic-calendar/academic-calendar.job.ts
  • services/api/batch/src/batch/jobs/batch-job.interface.ts
  • services/api/batch/src/batch/jobs/cafeteria/cafeteria.job.ts
  • services/api/batch/src/batch/jobs/notice/notice.job.ts
  • services/api/batch/src/batch/jobs/shuttle/__fixtures__/shuttle-timetable.html
  • services/api/batch/src/batch/jobs/shuttle/domain/shuttle-timetable.entity.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.client.spec.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.client.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.job.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.parser.spec.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.parser.ts
  • services/api/batch/src/batch/jobs/shuttle/shuttle.repository.ts
  • services/api/batch/src/batch/jobs/shuttle/type/parsed-shuttle-timetable.ts
  • services/api/batch/src/batch/utils/delay.ts
  • services/api/batch/src/main.ts
  • services/api/batch/tsconfig.build.json
  • services/api/batch/tsconfig.json

Comment thread services/api/batch/eslint.config.mjs Resolved
Comment on lines +17 to +23
if (timetables.length !== 2) {
throw new Error('셔틀 시간표 테이블을 찾을 수 없습니다.');
}

return {
updatedAt: this.extractUpdatedAt(raw),
timetables,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

파싱 실패 원인이 구분되지 않아 장애 분류가 깨집니다.

현재 구현은 Error 문자열에 의존하고, 검사 순서상 업데이트 일시 누락도 시간표 누락으로 처리될 수 있습니다. 이 상태로는 “누락 원인 구분(코드 기반)” 요구를 충족하기 어렵습니다.

ShuttleParseError(예: MISSING_UPDATED_AT, MISSING_TIMETABLE)를 도입하고, parse()에서 원인별로 명확히 분기해 던지도록 정리해 주세요.

🔧 제안 diff
+export class ShuttleParseError extends Error {
+  constructor(
+    public readonly code: 'MISSING_UPDATED_AT' | 'MISSING_TIMETABLE',
+    message: string,
+  ) {
+    super(message);
+  }
+}

   parse(raw: string): ParsedShuttleTimetablePage {
+    const updatedAt = this.extractUpdatedAt(raw);
     const timetables = this.extractTables(raw)
       .map((table) => this.parseTable(table))
       .filter(
         (timetable): timetable is ParsedShuttleTimetable => timetable !== null,
       );

     if (timetables.length !== 2) {
-      throw new Error('셔틀 시간표 테이블을 찾을 수 없습니다.');
+      throw new ShuttleParseError(
+        'MISSING_TIMETABLE',
+        '셔틀 시간표 테이블을 찾을 수 없습니다.',
+      );
     }

     return {
-      updatedAt: this.extractUpdatedAt(raw),
+      updatedAt,
       timetables,
     };
   }

   private extractUpdatedAt(raw: string): Date {
@@
     if (!updatedAtMatch) {
-      throw new Error('셔틀 시간표 최근 업데이트 일시를 찾을 수 없습니다.');
+      throw new ShuttleParseError(
+        'MISSING_UPDATED_AT',
+        '셔틀 시간표 최근 업데이트 일시를 찾을 수 없습니다.',
+      );
     }

Also applies to: 109-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/api/batch/src/batch/jobs/shuttle/shuttle.parser.ts` around lines 17
- 23, Introduce a custom ShuttleParseError exception class with specific error
codes (MISSING_UPDATED_AT and MISSING_TIMETABLE) to replace generic Error
strings and enable proper failure cause classification. In the parse() method,
reorganize validation checks to validate updatedAt extraction before timetables
validation, then throw the appropriate ShuttleParseError with the correct code
based on which validation fails. Update the timetable length check around line
17-23 to throw ShuttleParseError with MISSING_TIMETABLE code, and update the
updatedAt validation around lines 109-116 to throw ShuttleParseError with
MISSING_UPDATED_AT code instead of generic Error messages.

Comment thread services/api/batch/src/main.ts Resolved
@JangDongHo
JangDongHo merged commit 887a270 into dev Jun 15, 2026
1 check passed
@JangDongHo
JangDongHo deleted the feat/data-scraper branch June 15, 2026 02:36
@JangDongHo JangDongHo changed the title 셔틀 시간표 배치 수집 기반 구현 [Feat] 셔틀 시간표 배치 수집 기반 구현 Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Batch Feat Shuttle 셔틀버스 도메인

Projects

None yet

Development

Successfully merging this pull request may close these issues.

셔틀 시간표 배치 수집 기반 구현 (shuttle_timetable)

1 participant