diff --git a/docs/lottecinema-network-analysis-result.md b/docs/lottecinema-network-analysis-result.md index bd91ef66..4e7107ac 100644 --- a/docs/lottecinema-network-analysis-result.md +++ b/docs/lottecinema-network-analysis-result.md @@ -20,7 +20,8 @@ - `GetPlaySequence`에서 날짜/극장/영화 조합별 회차 실데이터 확보 - 남은 좌석 수 조회: `가능` - `GetPlaySequence`의 `TotalSeatCount`, `BookingSeatCount`로 계산 가능 - - 실측 기준 `remainingSeats = TotalSeatCount - BookingSeatCount` + - 실측 기준 `remainingSeats = BookingSeatCount` + - `bookedSeats = TotalSeatCount - BookingSeatCount` - 좌석 맵 상세 조회: `조건부 가능` - `GetSeats`로 좌석 맵은 조회되나, `BookingSeats`/`ScreenSeatInfo.BookingCount`와 `GetPlaySequence.BookingSeatCount`의 의미 차이가 있어 @@ -218,21 +219,25 @@ - `EndTime=12:47` - `ScreenNameKR=1관 샤롯데` - `TotalSeatCount=32` - - `BookingSeatCount=28` - - `남은 좌석수 = 4` + - `BookingSeatCount=28` (예매 가능/잔여 좌석) + - `남은 좌석수 = 28` + - `예매 좌석수 = 4` #### 잔여 좌석 계산식 ```text -remainingSeats = TotalSeatCount - BookingSeatCount +remainingSeats = BookingSeatCount +bookedSeats = TotalSeatCount - BookingSeatCount ``` +`BookingSeatCount` 필드명은 예매된 수처럼 보이지만, 공식 예매 화면과 `GetSeats` 점유 좌석 수와 대조하면 **예매 가능 좌석 수**다. + #### 실측 회차 예시 -- `10:40-12:47`, 총 32석, 예매 28석, 잔여 4석 -- `13:20-15:27`, 총 32석, 예매 27석, 잔여 5석 -- `16:00-18:07`, 총 32석, 예매 32석, 잔여 0석 -- `18:40-20:47`, 총 32석, 예매 22석, 잔여 10석 +- `10:40-12:47`, 총 32석, 잔여 28석, 예매 4석 +- `13:20-15:27`, 총 32석, 잔여 27석, 예매 5석 +- `16:00-18:07`, 총 32석, 잔여 32석, 예매 0석 +- `18:40-20:47`, 총 32석, 잔여 22석, 예매 10석 ### 2.3 `POST /LCWS/Ticketing/TicketingData.aspx` + `MethodName=GetInvisibleMoviePlayInfo` @@ -300,11 +305,10 @@ remainingSeats = TotalSeatCount - BookingSeatCount #### 주의점 - 동일 조건에서: - - `GetPlaySequence.BookingSeatCount = 28` - - `GetSeats.ScreenSeatInfo.BookingCount = 4` -- 따라서 현재 시점에서는 `GetSeats`의 `BookingSeats`/`BookingCount` 의미가 - "최종 판매 좌석 수"와 다를 가능성이 큼 -- 잔여 좌석 기능은 `GetPlaySequence` 기반으로 구현하는 편이 안전함 + - `GetPlaySequence.BookingSeatCount = 28` (잔여/예매 가능) + - `GetSeats.ScreenSeatInfo.BookingCount = 4` (실제 점유) +- 두 값은 모순이 아니라 의미가 다르다. `32 - 28 = 4`로 맞는다. +- 잔여 좌석 기능은 `GetPlaySequence.BookingSeatCount`를 그대로 쓰면 된다. ### 2.5 `POST /LCWS/Common/MainData.aspx` + `MethodName=GetPopupMessageOnLine` diff --git a/openapi.json b/openapi.json index c8691e12..6636c6eb 100644 --- a/openapi.json +++ b/openapi.json @@ -2726,11 +2726,11 @@ }, "bookedSeats": { "type": "integer", - "example": 28 + "example": 4 }, "remainingSeats": { "type": "integer", - "example": 4 + "example": 28 } } }, diff --git a/openapi.yaml b/openapi.yaml index 530454fd..66873ba4 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1920,10 +1920,10 @@ components: example: 32 bookedSeats: type: integer - example: 28 + example: 4 remainingSeats: type: integer - example: 4 + example: 28 LotteCinemaTheaterSearchResponse: type: object properties: diff --git a/src/pages/openapiSpecComponentsLottecinema.ts b/src/pages/openapiSpecComponentsLottecinema.ts index 3caf4126..57afd627 100644 --- a/src/pages/openapiSpecComponentsLottecinema.ts +++ b/src/pages/openapiSpecComponentsLottecinema.ts @@ -50,8 +50,8 @@ export const OPENAPI_LOTTECINEMA_COMPONENT_SCHEMAS = { startTime: { type: 'string', example: '10:40' }, endTime: { type: 'string', example: '12:47' }, totalSeats: { type: 'integer', example: 32 }, - bookedSeats: { type: 'integer', example: 28 }, - remainingSeats: { type: 'integer', example: 4 }, + bookedSeats: { type: 'integer', example: 4 }, + remainingSeats: { type: 'integer', example: 28 }, }, }, LotteCinemaTheaterSearchResponse: { diff --git a/src/services/lottecinema/client.ts b/src/services/lottecinema/client.ts index 81ef3517..346c14d3 100644 --- a/src/services/lottecinema/client.ts +++ b/src/services/lottecinema/client.ts @@ -160,7 +160,9 @@ async function fetchPlaySequenceByPair( .filter((item) => item.CinemaID && item.RepresentationMovieCode && item.ScreenID && item.PlaySequence) .map((item) => { const totalSeats = toNumber(item.TotalSeatCount); - const bookedSeats = toNumber(item.BookingSeatCount); + // 공식 BookingSeatCount는 예매된 수가 아니라 예매 가능(잔여) 좌석 수다. + const remainingSeats = toNumber(item.BookingSeatCount); + const bookedSeats = Math.max(totalSeats - remainingSeats, 0); const normalizedPlayDate = normalizeDateForOutput(item.PlayDt || playDate); const theaterId = String(item.CinemaID); const screenId = String(item.ScreenID); @@ -179,7 +181,7 @@ async function fetchPlaySequenceByPair( endTime: formatTime(item.EndTime), totalSeats, bookedSeats, - remainingSeats: Math.max(totalSeats - bookedSeats, 0), + remainingSeats, }; }); } diff --git a/tests/services/lottecinema/client.test.ts b/tests/services/lottecinema/client.test.ts index 548a08fc..f26559af 100644 --- a/tests/services/lottecinema/client.test.ts +++ b/tests/services/lottecinema/client.test.ts @@ -245,7 +245,10 @@ describe('fetchLotteCinemaNowShowing', () => { expect(result.theaters).toHaveLength(1); expect(result.movies).toHaveLength(1); expect(result.showtimes[0].scheduleId).toBe('20260310-1016-1201-1'); - expect(result.showtimes[0].remainingSeats).toBe(4); + // BookingSeatCount는 예매 가능(잔여) 좌석 수다. + expect(result.showtimes[0].remainingSeats).toBe(28); + expect(result.showtimes[0].bookedSeats).toBe(4); + expect(result.showtimes[0].totalSeats).toBe(32); }); it('movieId만 있으면 여러 극장을 순회해 회차를 모은다', async () => { diff --git a/tests/services/lottecinema/tools/getRemainingSeats.test.ts b/tests/services/lottecinema/tools/getRemainingSeats.test.ts index 192f75e8..5ccc0323 100644 --- a/tests/services/lottecinema/tools/getRemainingSeats.test.ts +++ b/tests/services/lottecinema/tools/getRemainingSeats.test.ts @@ -100,7 +100,8 @@ describe('createGetRemainingSeatsTool', () => { expect(parsed.count).toBe(2); expect(parsed.seats[0].startTime).toBe('10:40'); - expect(parsed.seats[0].remainingSeats).toBe(4); + expect(parsed.seats[0].remainingSeats).toBe(28); + expect(parsed.seats[0].bookedSeats).toBe(4); }); it('동일 시작 시간은 극장명으로 정렬한다', async () => { @@ -303,6 +304,7 @@ describe('createGetRemainingSeatsTool', () => { expect(parsed.filters.theaterId).toBe('9001'); expect(parsed.resolvedTheater.theaterName).toBe('안산중앙'); - expect(parsed.seats[0].remainingSeats).toBe(4); + expect(parsed.seats[0].remainingSeats).toBe(28); + expect(parsed.seats[0].bookedSeats).toBe(4); }); });