Metadata-Version: 2.4
Name: posco-lp
Version: 1.2.5
Summary: POSCO Learning Platform Crawling Code Library
Home-page: https://github.com/hrdkdh
Author: Duhwan Kim
Author-email: hrdkdh@naver.com
Project-URL: Bug Tracker, https://github.com/hrdkdh
Keywords: POSCO,learning platform,lp
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: requests
Requires-Dist: urllib3
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: openpyxl
Requires-Dist: bs4
Provides-Extra: mcp
Requires-Dist: mcp; extra == "mcp"
Requires-Dist: python-dotenv; extra == "mcp"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# posco-lp

포스코 Learning Platform(LP) 관리자 기능을 자동화하기 위한 파이썬 패키지입니다.
관리자 권한이 있는 계정으로만 정상적인 사용이 가능합니다.

## 설치 및 업데이트

```bash
pip install posco-lp
```

소스 폴더에서 직접 설치하려면 아래와 같이 실행합니다.

```bash
pip install .
```

주요 의존성은 `requests`, `pandas`, `numpy`, `bs4`, `openpyxl`입니다. Codex MCP 서버까지 함께 쓰려면 `pip install "posco-lp[mcp]"` 또는 `pip install mcp python-dotenv`를 추가로 실행하면 됩니다.

기존 사용자 업데이트는 아래와 같이 진행하면 됩니다.

```bash
python -m pip install --upgrade posco-lp
```

MCP 서버까지 함께 쓰는 환경이라면 extra 의존성까지 포함해 아래처럼 업데이트하는 편이 가장 안전합니다.

```bash
python -m pip install --upgrade "posco-lp[mcp]"
```

저장소 소스를 직접 설치해 쓰는 경우에는 저장소 루트에서 아래처럼 업데이트할 수 있습니다.

```bash
python -m pip install --upgrade .
```

MCP extra까지 함께 반영하려면 아래 명령을 사용합니다.

```bash
python -m pip install --upgrade ".[mcp]"
```

설치 또는 업데이트 후 버전 확인 예시는 아래와 같습니다.

```bash
python -c "import poscolp; print(poscolp.__version__)"
```

## 빠른 시작

```python
from poscolp import LP, __version__

print(__version__)  # 1.2.5

lp = LP(lp_id="your_id", lp_pw="your_password")

company_df = lp.getCompanyList()
user_df = lp.getUserList(search_text="홍길동")
cha_df = lp.getChaList(search_text="리더십", start_date="20260101", end_date="20261231")
```

메시지 출력이 필요한 경우에만 `verbose=True`를 사용하면 됩니다.

```python
lp = LP(lp_id="your_id", lp_pw="your_password", verbose=True)

# 또는 메서드별로만 출력 활성화
user_df = lp.getUserList(search_text="홍길동", verbose=True)
```

엑셀 저장이 필요한 경우 `filename_to_save` 파라미터를 사용하면 되며, 확장자를 생략하면 `.xlsx`가 자동으로 붙습니다.

## 1.2.5에서 달라진 점

- `getLectureRoomInfo()`가 강의실 예약정보뿐 아니라 강의실 메타정보도 함께 반환하도록 확장되었습니다. 교육지역코드, 건물코드/건물명, 강의실코드/강의실명, 층수, 호실, 수용가능 인원(`numberCnt`), 운영자수(`managerCnt`), 예약 가능 관련 플래그를 함께 확인할 수 있습니다.
- `getLectureRoomInfo()`는 예약번호(`reserveNo`), 예약자명(`예약자`), 예약자ID(`userId`), 실제 예약일자(`eduDts`)도 함께 펼쳐 반환합니다. LP가 개별 예약일자를 비워 보내는 경우에는 조회 기준일을 예약일자로 사용합니다.
- `lp_get_lecture_room_info()` 설명과 MCP 문서를 보강해, Agent가 강의실 정원/코드/층수/예약자명/예약번호 같은 컬럼을 활용하기 쉽게 정리했습니다.

## 1.2.4에서 달라진 점

- 조회 전용 MCP 래퍼 전반의 입력 보정을 확대했습니다. `lp_get_company_list()`, `lp_get_user_list()`, `lp_get_user_history()`, `lp_get_cha_list()`, `lp_get_lecture_room_info()`가 alias, 날짜 형식, 상대 날짜 표현을 더 폭넓게 해석합니다.
- `lp_get_user_list()`는 `company_code=''`(전체), `company_code='0'`(POSCO), `포스코`, `전체`, `all`, `재직`, `퇴직`, `이름`, `아이디`, `직번` 같은 실사용 입력을 정규화합니다.
- `lp_get_user_history()`는 `LP관리ID`, `아이디`, `직번` alias를 정규화하고, `search_text`가 비어 있으면 LP에 요청하지 않고 `validation_error`를 반환합니다.
- `lp_get_cha_list()`는 `YYYYMMDD`, `YYYY-MM-DD`, `YYYY/MM/DD`, `YYYY.MM.DD`, `오늘`, `어제`, `내일` 형식의 날짜를 정규화하고, `year="2026"`, `year="올해"` 같은 입력을 해석합니다. 날짜가 일부 비어 있으면 자동 보정하고, 범위가 뒤집혀 있으면 안전하게 교정합니다.
- `lp_get_cha_detail()`와 교육생/설문 조회 도구들은 `course_id`, `cha_id`가 비어 있으면 LP에 요청하지 않고 바로 `validation_error`를 반환합니다.
- MCP 응답에는 최종 해석된 `request_args`와 자동 보정 내역인 `normalization_notes`가 함께 담겨 디버깅이 쉬워졌습니다.


## 1.2.3에서 달라진 점

- 조회 전용 `poscolp.mcp_server`를 추가해 Codex에서 LP 데이터를 MCP 툴로 직접 조회할 수 있게 했습니다.
- MCP 서버가 `~/.codex/.env`를 우선 읽도록 해, 사용자별 LP 계정 정보를 저장소 밖에서 전역으로 관리할 수 있게 했습니다.
- `setup.py`에 `mcp` extra 의존성을 추가해 `pip install "posco-lp[mcp]"`만으로 MCP 서버 실행에 필요한 `mcp`, `python-dotenv`를 함께 설치할 수 있게 했습니다.
- Codex MCP 설정 방법과 read-only 도구 목록을 README에 정리했습니다.

## 1.2.2에서 달라진 점

- `getChaList()` 요청을 최신 LP 화면 기준 payload로 정리했습니다. 차수명 검색어는 `courseCsName`으로 전송하고, 기간 구분은 `eduPeriodFlag='M'`으로 조회합니다.
- `getStudentRegisterList()` 요청을 최신 LP 화면 기준 payload로 보강했습니다. 응답에 `hpNo`가 있으면 `휴대폰` 컬럼으로 함께 반환합니다.
- `getUserList()` 요청을 최신 사용자 검색 화면 기준으로 정리했습니다. `stay_type` 파라미터로 재직/퇴직 조회를 구분할 수 있고, 퇴직자 응답처럼 일부 컬럼이 축약된 경우에도 안전하게 반환합니다.
- `getUserHistory(search_type="NUM")`는 POSCO 고정 필터를 강제로 쓰지 않고, 최신 사용자 검색 결과에서 LP관리ID를 확인한 뒤 교육이력을 조회합니다.

## 1.2.1에서 달라진 점

- `getLectureRoomInfo()` 요청의 `searchEduArea`, `searchEduPlaceCode`를 공란으로 전송하도록 수정했습니다.
- 강의실 예약 조회는 특정 교육지역/교육장 코드에 묶이지 않고 LP 기본 조회 범위를 따릅니다.

## 1.2.0에서 달라진 점

- `pyautogui` 의존성을 제거했습니다.
- 로그인 정보가 없을 때 더 이상 GUI 입력창을 띄우지 않습니다.
- 로그인 필요한 메서드는 내부적으로 자동 로그인 시도를 먼저 수행합니다.
- `getUserList()`는 `search_text`와 `company_code`를 모두 비우면 최신 LP 화면과 동일하게 전체 조회를 수행합니다. `company_code=''`는 전체, `company_code='0'`는 POSCO를 의미합니다.
- 수료자 조회/일괄 수료 처리에서 LP HTML 파싱 실패 시 안전하게 종료하도록 보강했습니다.
- `getLectureRoomInfo()`의 잘못된 요청 코드와 빈 결과 처리 로직을 정리했습니다.
- `print()` 기반 안내 메시지는 기본적으로 출력하지 않으며, `verbose=True`일 때만 출력합니다.
- 버전 정보는 `poscolp/__version__.py`를 단일 원본으로 사용하고, `setup.py`는 그 값을 읽어 배포합니다.

## Codex MCP 서버

이 저장소에는 조회 전용 MCP 서버 모듈 `poscolp.mcp_server`가 포함되어 있습니다. Codex에서 붙이면 LP 회사/사용자/차수/교육생/설문/강의실 조회를 툴 호출 형태로 직접 수행할 수 있습니다. 반대로 `updateStudentToPassed()`, `resetUserPassword()`처럼 LP 데이터를 변경하는 메서드는 안전을 위해 MCP에 노출하지 않았습니다.

서버는 시작 시 `~/.codex/.env`를 가장 먼저 찾고, 없으면 현재 작업 폴더의 `.env`, 패키지 루트의 `.env` 순서로 자격 정보를 찾습니다. 사용자별 전역 설정이 목적이라면 `~/.codex/.env` 사용을 권장합니다. 기존 테스트용 `.env` 키도 계속 지원합니다.

- 추천 아이디 키: `POSCOLP_LP_ID`
- 추천 비밀번호 키: `POSCOLP_LP_PW`
- 하위 호환 키: `LP_ID`, `LP_PW`, `LP_ID_FOR_TEST`, `LP_PW_FOR_TEST`
- 선택 옵션: `POSCOLP_MCP_VERBOSE=true`를 주면 내부 verbose 출력을 활성화할 수 있습니다.

권장 `~/.codex/.env` 예시는 아래와 같습니다.

```env
POSCOLP_LP_ID=your_lp_id
POSCOLP_LP_PW=your_lp_password
```

MCP 조회 도구들은 공통적으로 아래 규칙으로 입력을 보정합니다.

- `lp_get_company_list()`는 `group`, `partner`, `협력`, `해외`, `overseas` 같은 alias를 각각 `그룹사`, `협력사`, `해외법인`으로 정규화합니다.
- `lp_get_user_list()`는 `search_type`의 `이름/아이디/직번`, `stay_type`의 `재직/퇴직`, `company_code`의 `전체/all=''`, `포스코/POSCO='0'` alias를 정규화합니다. `search_text`는 비워 둘 수 있습니다.
- `lp_get_user_history()`는 `LP관리ID`, `아이디`, `직번` alias를 정규화하고, `search_text`가 비어 있으면 LP 요청 대신 `validation_error`를 반환합니다.
- `lp_get_cha_list()`는 `start_date`, `end_date`에 `YYYYMMDD`, `YYYY-MM-DD`, `YYYY/MM/DD`, `YYYY.MM.DD`, `오늘`, `어제`, `내일`을 받을 수 있고, `year`에는 `2026`, `올해`, `작년`, `내년`을 넣을 수 있습니다. 날짜가 하나만 있으면 같은 날로 보정하고, 시작일이 종료일보다 늦으면 자동으로 교체합니다.
- `lp_get_cha_detail()`와 교육생/설문 조회 도구들은 `course_id`, `cha_id`가 비어 있으면 LP에 요청하지 않고 `validation_error`를 반환합니다.
- `lp_get_lecture_room_info()`는 상대 날짜 표현도 `YYYYMMDD`로 정규화하며, 응답에는 수용가능 인원(`numberCnt`), 강의실코드, 층수, 예약자명(`예약자`), 예약번호, 예약자ID, 실제 예약일자(`eduDts`) 같은 강의실 메타정보가 함께 포함됩니다. 해석 불가능한 날짜는 `validation_error`로 즉시 돌려줍니다.
- 모든 MCP 응답에는 최종 해석된 인자가 `request_args`로 들어가며, 자동 보정이 있었다면 `normalization_notes`도 함께 반환됩니다.


직접 실행 예시는 아래와 같습니다.

```bash
python -m poscolp.mcp_server
```

이미 `posco_lp` MCP 서버를 Codex에 연결해 사용 중이었다면, 패키지 업데이트 후에는 아래 순서로 반영하는 것을 권장합니다.

1. `posco-lp`가 실제 설치된 동일한 파이썬 환경에서 `python -m pip install --upgrade posco-lp` 또는 `python -m pip install --upgrade "posco-lp[mcp]"`를 실행합니다.
2. `~/.codex/config.toml`의 `mcp_servers.posco_lp.command`가 바로 그 파이썬 인터프리터를 가리키는지 확인합니다.
3. 설치본 기준 사용을 원한다면 `cwd`는 패키지 저장소가 아닌 중립 경로로 유지합니다.
4. Codex를 재시작하거나 MCP 서버 연결을 새로 열어 변경 내용을 반영합니다.
5. 필요하면 `lp_server_status`를 호출해 MCP 서버가 읽은 패키지 버전과 자격정보 상태를 다시 확인합니다.

Codex 설정 예시는 아래와 같습니다. 전역으로 쓰려면 `~/.codex/config.toml`에, 특정 프로젝트에만 쓰려면 프로젝트 루트의 `.codex/config.toml`에 같은 블록을 넣으면 됩니다. 설치된 패키지를 우선 사용하려면 `posco-lp`가 설치된 파이썬 인터프리터를 `command`로 지정하고, `cwd`는 저장소 루트가 아닌 중립 경로로 두는 것을 권장합니다. `~/.codex/.env`를 쓰는 경우에는 `cwd`가 어디를 가리키든 같은 자격정보를 사용할 수 있습니다.

```toml
[mcp_servers.posco_lp]
command = "C:\\Users\\poscouser\\venv\\venv_common\\Scripts\\python.exe"
args = ["-m", "poscolp.mcp_server"]
cwd = "C:\\Users\\poscouser"
```

기본적으로 목록형 MCP 툴은 `row_limit=200`까지 반환하며, 응답에는 `total_row_count`와 `truncated`가 함께 들어갑니다. 더 많이 받고 싶으면 각 툴 호출에서 `row_limit` 값을 늘리면 됩니다.

지원 툴은 아래와 같습니다.

- `lp_server_status`
- `lp_get_company_list`
- `lp_get_user_list`
- `lp_get_user_history`
- `lp_get_cha_list`
- `lp_get_cha_detail`
- `lp_get_student_register_list`
- `lp_get_student_passed_list`
- `lp_get_survey_mean_score`
- `lp_get_survey_answer_list`
- `lp_get_lecture_room_info`

## 로그인 및 메시지 동작

- `LP(lp_id, lp_pw)` 형태로 생성하면 인증이 필요한 메서드에서 자동 로그인됩니다.
- `LP()`처럼 로그인 정보 없이 객체를 생성할 수는 있지만, 인증이 필요한 메서드는 실패 처리됩니다.
- 기본 동작은 조용한 모드이며, 안내/경고/오류 메시지는 출력하지 않습니다.
- 생성자에서 `verbose=True`를 주면 해당 객체의 메서드 전반에 출력이 활성화됩니다.
- 특정 호출에서만 메시지가 필요하면 각 메서드의 `verbose=True`를 사용하면 됩니다.
- 대부분의 조회 메서드는 로그인 실패 시 `False`를 반환합니다.
- `getUserHistory()`는 `search_text`가 비어 있으면 `None`을 반환합니다.
- `updateStudentToPassed()`, `resetUserPassword()`는 반환값 구조를 바꾸지 않으며 `None`을 반환합니다. 처리 메시지는 `verbose=True`일 때만 출력합니다.

## 메서드 명세

### 공통

- `login(verbose=False)`
  - LP 로그인 세션을 생성합니다.
  - 로그인 진행 및 실패 메시지는 `verbose=True`일 때만 출력합니다.

### 회사/사용자 조회

- `getCompanyList(company_cate="그룹사", filename_to_save="", verbose=False)`
  - 그룹사, 협력사, 해외법인 목록을 `DataFrame`으로 반환합니다.
- `getUserList(search_text="", search_type="NAME", company_code="", filename_to_save="", verbose=False, stay_type="N")`
  - 사용자 검색 결과를 `DataFrame`으로 반환합니다.
  - `search_type`은 `NAME`, `ID`, `NUM`을 지원합니다.
  - `stay_type`은 `N`/`재직`, `Y`/`퇴사`/`퇴직`을 지원합니다.
  - 최신 LP 화면 기준으로 `searchCompType`, `searchDepartText`, `searchPiumTagtCdText`, `searchStayType`를 포함한 요청을 사용합니다.
  - `search_text`와 `company_code`를 모두 비우면 전체 조회를 수행합니다. `company_code=''`는 전체, `company_code='0'`는 POSCO를 의미합니다.
  - 퇴직자 조회 응답은 일부 상세 컬럼이 없을 수 있으며, `retrDt`가 있으면 `퇴직일` 컬럼으로 함께 반환합니다.
- `getUserHistory(search_type="LP_USER_ID", search_text="", filename_to_save="", verbose=False)`
  - 특정 사용자의 교육 이력을 반환합니다.
  - `search_type="NUM"`일 때는 최신 사용자 검색 결과에서 LP관리ID를 먼저 확인한 뒤 조회하며, POSCO 고정 필터를 강제로 사용하지 않습니다.
  - `search_text`가 비어 있으면 조회를 수행하지 않고 `None`을 반환합니다.
- `resetUserPassword(lp_user_id, verbose=False)`
  - 특정 사용자의 LP 비밀번호를 초기화합니다.
  - 처리 결과 메시지는 `verbose=True`일 때만 출력합니다.

### 과정/차수/교육생

- `getChaList(group_name="", search_text="", start_date="", end_date="", simple=True, filename_to_save="", verbose=False)`
  - 차수 목록을 반환합니다.
  - 최신 LP 화면 기준으로 `courseCsName`, `eduMngtDeptName`, `searchStartDt/searchEndDt`, `eduPeriodFlag='M'` 형태의 조회 요청을 사용합니다.
- `getChaDetail(courseId, chaId, verbose=False)`
  - 차수 상세 정보를 `dict`로 반환합니다.
- `getStudentRegisterList(courseId, chaId, filename_to_save="", verbose=False)`
  - 차수 등록자 목록을 반환합니다.
  - 최신 LP 화면 기준으로 `draw`, `search[value]`, `searchPropStatCode`, `searchClassSeq`, `companyInfo`, `keyword`, `courseOpenScope`, `eduMngtDept`, `showOthGrpYn`을 포함한 조회 요청을 사용합니다.
  - 반복되는 `columns[n][...]` 메타데이터는 서버에서 생략 가능하여 포함하지 않습니다.
  - LP 응답에 성별(sexdstn) 또는 휴대폰(hpNo) 컬럼이 없으면 해당 컬럼을 제외하고 반환합니다.
- `getStudentPassedList(courseId, chaId, filename_to_save="", verbose=False)`
  - 차수 수료자 목록을 반환합니다.
  - LP 화면 구조가 바뀌어 파싱에 실패하면 `False`를 반환할 수 있으며, 관련 메시지는 `verbose=True`일 때만 출력합니다.
- `updateStudentToPassed(courseId, chaId, verbose=False)`
  - 수료 조건 대상자를 일괄 수료 처리합니다.
  - 처리 결과 메시지는 `verbose=True`일 때만 출력합니다.

### 설문/강의실

- `getSurveyMeanScore(courseId, chaId, filename_to_save="", verbose=False)`
  - 설문별 평균 점수와 응답자 수를 반환합니다.
- `getSurveyAnswerList(courseId, chaId, filename_to_save="", verbose=False)`
  - 설문 응답 원본 데이터를 반환합니다.
- `getLectureRoomInfo(date, filename_to_save="", verbose=False)`
  - 특정 날짜의 강의실 예약 정보를 반환합니다.
  - 요청 시 `searchEduArea`, `searchEduPlaceCode`는 공란으로 전달합니다.
  - 결과에는 건물코드/강의실코드, 층수, 호실, 수용가능 인원(`numberCnt`), 운영자수(`managerCnt`), 예약 가능 관련 플래그, 예약자명(`예약자`), 예약번호(`reserveNo`), 예약자ID(`userId`), 실제 예약일자(`eduDts`)를 함께 담습니다.
  - LP가 개별 예약일자를 비워 보내는 경우에는 조회 기준일을 예약일자로 사용합니다.
  - 날짜 형식 오류 메시지는 `verbose=True`일 때만 출력합니다.

## 반환 규칙 요약

- 조회 성공: 주로 `pandas.DataFrame` 또는 `dict`
- 조회 실패: 주로 `False`
- 입력이 비어 조회 자체를 진행하지 않는 경우: 일부 메서드는 `None`
- 처리형 메서드: 반환값은 그대로 `None`, 처리 메시지는 `verbose=True`일 때만 출력

## 주요 변경 이력

- `1.2.5` | 2026-07-07
  - `getLectureRoomInfo()` 결과에 강의실 메타정보 확장
  - 수용가능 인원(`numberCnt`), 강의실코드, 층수, 운영자수, 예약 가능 관련 플래그 추가
  - 예약자명(`예약자`), 예약번호(`reserveNo`), 예약자ID(`userId`), 실제 예약일자(`eduDts`) 출력 보강
  - `lp_get_lecture_room_info()` MCP 설명 및 문서 정비

- `1.2.4` | 2026-07-03
  - 조회 전용 MCP 래퍼 전반의 alias/필수값/날짜 검증 보강
  - `lp_get_cha_list()`의 `year` 파라미터, 날짜 자동 정규화, 뒤집힌 범위 자동 교정 지원
  - `lp_get_user_list()`의 회사코드/재직여부/검색타입 alias 보강
  - `lp_get_user_history()` 공란 입력의 `validation_error` 반환 및 `LP관리ID` alias 보강
  - `lp_get_cha_detail()` 및 교육생/설문 조회 도구의 필수 ID 검증 추가
  - MCP 응답에 `request_args`, `normalization_notes` 추가

- `1.2.3` | 2026-07-03
  - 조회 전용 `poscolp.mcp_server` 추가
  - MCP 서버의 `~/.codex/.env` 우선 자격정보 로딩 지원
  - `mcp`, `python-dotenv` optional dependency 및 `posco-lp[mcp]` 설치 경로 추가
  - Codex MCP 설정 및 read-only 도구 문서화
- `1.2.2` | 2026-07-03
  - `getChaList()`, `getStudentRegisterList()`, `getUserList()` 요청을 최신 LP 화면 기준으로 갱신
  - `getUserList()`에 `stay_type` 지원 및 퇴직자 응답 안전 처리 추가
  - `getStudentRegisterList()` 휴대폰 컬럼 지원
  - `getUserHistory(search_type="NUM")`의 POSCO 고정 필터 제거
- `1.2.1` | 2026-06-26
  - `getLectureRoomInfo()`의 강의실 조회 요청에서 교육지역/교육장 하드코딩 제거
- `1.2.0` | 2026-06-26
  - `pyautogui` 제거
  - 공통 로그인 가드 추가
  - 로그인 정보 미입력 시 경고 후 실패 처리
  - `openpyxl` 의존성 추가
  - `verbose` 기반 메시지 출력 제어 추가
  - README 및 docstring 정비
- `1.1.3` | 2023-09-21
  - `getStudentRegisterList()`에서 성별 컬럼 누락 시 발생하던 오류 수정
- `1.1.2` | 2023-08-31
  - `getStudentRegisterList()` 결과에 신청/변경일시와 성별 컬럼 추가
- `1.1.1` | 2023-07-16
  - `getUserHistory()` 메서드 추가
- `1.0.9` | 2023-06-01
  - `getChaList()` 결과에 교육지역 정보 추가
- `1.0.8`
  - `getSurveyAnswerList()` 메서드 추가
  - 저장 파일명에 확장자가 없으면 `.xlsx` 자동 추가

## 주의 사항

- POSCO Learning Platform 관리자 권한이 있는 계정이 필요합니다.
- 일부 메서드는 LP 관리자 페이지의 HTML 구조에 의존합니다.
- 패키지 내부 요청은 기존 구현과 동일하게 `verify=False`로 동작합니다.
- 자동화 코드에 이미 이전 버전을 사용 중이라면, 업그레이드 전 `verbose` 사용 여부와 실패 시 반환값 흐름을 한 번 점검하는 것을 권장합니다.
