Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions docs/lottecinema-network-analysis-result.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
- `GetPlaySequence`에서 날짜/극장/영화 조합별 회차 실데이터 확보
- 남은 좌석 수 조회: `가능`
- `GetPlaySequence`의 `TotalSeatCount`, `BookingSeatCount`로 계산 가능
- 실측 기준 `remainingSeats = TotalSeatCount - BookingSeatCount`
- 실측 기준 `remainingSeats = BookingSeatCount`
- `bookedSeats = TotalSeatCount - BookingSeatCount`
- 좌석 맵 상세 조회: `조건부 가능`
- `GetSeats`로 좌석 맵은 조회되나, `BookingSeats`/`ScreenSeatInfo.BookingCount`와
`GetPlaySequence.BookingSeatCount`의 의미 차이가 있어
Expand Down Expand Up @@ -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`

Expand Down Expand Up @@ -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`

Expand Down
4 changes: 2 additions & 2 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2726,11 +2726,11 @@
},
"bookedSeats": {
"type": "integer",
"example": 28
"example": 4
},
"remainingSeats": {
"type": "integer",
"example": 4
"example": 28
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/pages/openapiSpecComponentsLottecinema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 4 additions & 2 deletions src/services/lottecinema/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -179,7 +181,7 @@ async function fetchPlaySequenceByPair(
endTime: formatTime(item.EndTime),
totalSeats,
bookedSeats,
remainingSeats: Math.max(totalSeats - bookedSeats, 0),
remainingSeats,
};
});
}
Expand Down
5 changes: 4 additions & 1 deletion tests/services/lottecinema/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
6 changes: 4 additions & 2 deletions tests/services/lottecinema/tools/getRemainingSeats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
});
});