2026-09-23(수) 세션 — 자재관리 동기화 20시간 먹통(SSM 97KB 한도) · 파싱 규칙 정본
운영배포
93d7ce1완료 · devplanpfs-orderpaper-sync-ssm-limit.html· [코디]
지시
[페페] 「pfs 자재관리 항목에 나진 상가 등 새로 발주낸 내용을 엑셀파일 수정해서 담았는데 반영이 안된거 같다. 파싱 규칙 파악해서 조치해줘. 기존 파일에 새탭이 생성되거나 엑셀파일 기존 pfs에 데이터 올라간것도 원본 엑셀파일에서 내용이 수정되면 반영이 되는것을 기본으로 해야한다.」 + 「운영배포 사항이다」(채팅 승인).
결론 — 파싱 규칙 문제가 아니었다
운반이 끊겨 있었다. NAS 파일은 제대로 고쳐져 있었고(나진 9.22 16:51 저장, 새 탭 「코파산업」 포함), 그걸 운영 RDS 로 실어나르는 SSM 경로가 2026-09-22 13:39 부터 20시간 전량 실패하고 있었다.
An error occurred (MaxDocumentSizeExceeded) when calling the SendCommand operation:
The total size of your parameter(s) and document exceeds the 97KB limit.
- 운영 RDS 는 로컬 직결 불가 →
orderpaper_sync.py가 파싱 결과를 원격 스크립트에 gzip+base64 로 구워 SSM 으로 EC2 에 보낸다. - 현장이 늘어 그 스크립트가 74,276바이트 → base64 99,036바이트. 한도(97KB)에 문서·파라미터 오버헤드를 더하면 초과.
- 🔴아무도 몰랐던 게 더 크다 — 예약작업
orderpaper_autosync(30분)는wscript로 돌아 내부 실패와 무관하게 LastTaskResult=0. 실패는_autosync.log에만 쌓였다. 사이드카 릴레이도[relay] req=30 error한 줄만 남기고 끝.
고친 것 (파싱 규칙은 한 줄도 안 건드렸다)
| 무엇 | 어떻게 | 파일 |
|---|---|---|
| 청크 전송 | 60,000자 초과 시 여러 send-command 로 /tmp 에 이어붙인 뒤 디코드·실행 (S3 버킷 추가 0) |
scripts/ssm_run.py |
| 원격 파일명 고유화 | /tmp/_op_remote.<ms>_<pid>.py |
〃 |
| 크기 대조 가드 | b64 길이·디코드본 크기를 원본과 비교, 어긋나면 실행 거부 | 〃 |
| 실패 알림 | 동기화 실패 시 텔레그램(notify.py done) |
orderpaper-sync/orderpaper_sync.py |
🔴고유화가 없으면 깨진다 — 첫 시도에서 실측했다. 고정 이름(/tmp/_op_remote.py)으로 청크를 붙였더니
디코드본이 74,276 → 103,552바이트(청크 1개가 중복 append)가 되며 SyntaxError. 예약작업(09:38)과 내 수동 실행(09:41)이 겹친 결과다.
그래서 크기 대조 가드를 같이 넣었다 — 반만 디코드된 파이썬이 돌면 RDS 를 어중간하게 건드린 채 죽는다.
검증
- 운영 동기화 성공 —
ins 12 · upd 2,192 · removed 0· alive 2,158행 / 47현장 / 매핑 52·54. - 용산 나진 상가(field
2183) 운영 실조회 = 상원보드 23 · 코파산업 2 · 효산엘피엘 9 · 선재기업 9. 🔑「코파산업」 = 페페가 새로 만든 탭이 그대로 새 발주처로 INSERT 됐다(휨방지 철물 2200mm 12세트 + 퀵 발송 메모). - 밀려 있던 신규 12건 = 나진 5 · 라인VIP 개인건 6 · 금호 구미 형곡3차 1.
- 운영
/api/health={"db":true,"env":"live","ok":true}· 사이트맵 재빌드 = 그룹 8 · 페이지 60 · 미반영 1(화면 밖, 기존). - 조회 스크립트 =
C:\dev\pfs\orderpaper-sync\_q_najin.py(읽기 전용).
파싱 규칙 — 정본 요약 (orderpaper-sync/orderpaper_parse.py)
| 단계 | 규칙 |
|---|---|
| 대상 | \\192.168.0.101\공유폴더\2026현장\원부자재발주서\MM월\*.xlsx — 05월 이상 월폴더만 · ~$ 제외 |
| 파일명 | 첫 토큰=가구업체 / 나머지=현장. 「원부자재발주서」 제거 · 선행 (마커)·추가·복사본 은 발주처로 안 본다 · 넥서스→한샘 정규화 |
| 탭 | 탭 1개 = 발주처 1개. 제외 = 업체·발주서·설정·al마감사이즈·al업체·유리업체·poin_info. 그 밖 새 탭은 전부 새 발주처로 수집 |
| 섹션 | A열/C열 「포인 발주서」 제목마다 분리(한 탭에 여러 발주 가능) |
| 날짜 | 「수신」 행 = 발주처(C열)+발주일 · 「참조」 행 = 입고일 |
| 표 | 「NO」 행이 머리. 머리에 「원자재」 있으면 R(두께·수종·전면·후면·발주량), 없으면 S(품명·규격·발주량·입고일·비고) |
| 엣지 | 품명·규격에 「엣지」 있으면 E 블록으로 자동분리(「엣지바」는 AL 이라 제외) |
| 입고처 | A열 「입고처」 행 = 그 위쪽 항목들의 입고처(여러 개면 각자 자기 위 묶음을 끊는다) |
| 안 읽음 | NO칸이 글자인 라벨행 · 품명 0 · 품명·규격·발주량 전부 빈칸 · 원자재 템플릿 자리행 · 품명칸 병합이 규격칸까지 덮은 「시트」 메모행 |
| 현장연결 | 파일명 현장 ↔ field.name 토큰 2개 이상 일치면 자동, 수동 연결은 보존 |
반영 원리 — 항목 신분증 = (파일명 · 탭 · 섹션번호 · 자재구분 · 표 안 순번) 해시.
같으면 UPDATE(값 전부 최신 엑셀로), 없으면 INSERT, 이번 라운드에 안 나오면 remove_yn='1'.
보존(덮어쓰지 않음) = 진행상태(대기/진행/완료) · 사용자가 바꾼 자재구분·입고일 · 재고(stock).
반영이 안 되는(어긋나는) 경우 — 지금도 유효
- 🔴날짜칸을 비우면 화면에서 사라진다 — 자재관리는 입고일‖발주일 기준 3개월 창만 본다. 비면 양식 기본값(1월)이 들어가 창 밖으로 밀린다. 현재 숨김 유발 23블록 · 날짜 미기입 32블록(동기화 로그 끝 경고 목록).
- 🔴표 중간에 행을 끼우면 아래 항목 순번이 밀려 신분증이 바뀐다 → 값은 맞지만 진행상태·입고 체크가 한 칸씩 옮겨붙는다. 추가는 맨 뒤가 안전.
- 🔴파일명을 바꾸면 전부 새 항목 + 옛 항목 삭제표시(진행상태 초기화).
2부 — 날짜 고침 패널 + 엑셀 되쓰기 (같은 날 후속, 테스트서버만 반영)
지시
[페페] 「자재관리 탭, 발주서 정리 탭에 상단에 별도 블록으로 만들어서 날짜 없는 내용들을 올려주고 경고 표시해줘. 그리고 거기서 날짜수정하면 엑셀파일에도 지정된 날짜 입력하고 저장해줘. 엑셀이 읽기모드인 경우에는 닫힘 신호 기다렸다가 그 후에 작업 진행해줘. 1시간 이상 대기시 파일 이름을 나에게 텔레그램으로 알려줘라.」 + 「테스트서버 데이터 운영데이터로 최신화 하고 테스트서버 반영만 먼저 반영해」.
그릴링 1문 [페페] 결정 = 입고일 칸의 「긴급」 63칸은 경고에 올리고, 날짜를 넣으면 덮어쓴다 (화면에 현재값을 같이 보여준 뒤 교체 — 납기가 정해졌다는 뜻이니 「긴급」은 역할을 다한 값이다).
먼저 한 것 — 테스트 데이터 최신화
운영 orderpaper(2,758행)·orderpaper_site_map(60행)을 테스트 RDS 로 복제(_op_copy_prod_to_test.py, 운영은 읽기만).
그 뒤 테스트 동기화 1회 → alive 2,158행 / 47현장으로 운영과 같아졌다.
⚠단 field_no 는 환경마다 다르다(테스트 field 테이블 기준으로 다시 매핑된다 — 「용산 나진 상가」는 테스트에 현장이 없어 미연결).
설계를 바꾼 발견
🔴블록의 엑셀 위치가 DB 에 없었다. block_key 는 sha1(source_file|sheet|section|material_type) 이라 되돌릴 수 없고,
sheet·section 은 해시 입력으로만 쓰이고 버려졌다 — 「이 블록이 어느 탭 몇 번째 발주인가」를 알 길이 없으니
엑셀을 고쳐 쓸 수가 없다. 파서가 이미 아는 값을 안 저장해서 생긴 구멍이다.
→ 컬럼 4개 추가: sheet_name · section_idx + order_date_raw · receive_date_raw(날짜칸의 비날짜 원문 = 「긴급」).
만든 것
| 층 | 파일 | 내용 |
|---|---|---|
| 파서 | orderpaper-sync/orderpaper_parse.py |
날짜칸(I열) 원문 보존 — raw_date_cell() |
| 동기화 | orderpaper-sync/orderpaper_sync.py |
컬럼 4개 적재(DDL·INSERT·UPDATE) |
| 테이블 | orderpaper-sync/orderpaper_date_fix_migrate.py |
orderpaper_date_fix 큐(13컬럼) |
| API | api/pfs/orderpaper/{dao,service,controller} |
date-issues · date-fix · date-fixes · date-fix/claim · date-fix/finish |
| 게이트 | web/__init__.py |
에이전트 화이트리스트 2줄 |
| 엑셀 쓰기 | orderpaper-sync/orderpaper_date_write.py |
zip XML 치환 + 백업 + 검증 + 잠김 판정 |
| 사이드카 | orderpaper-sync/orderpaper_{date_fix_relay,sync_server}.py |
20초 폴링 · 잠김 대기 · 1시간 텔레그램 |
| 화면 | web/templates/basic/field/field_orderpaper.html |
#opDateFixPanel |
🔴 함정 3개 (전부 실측으로 드러났다)
- openpyxl 로 열었다 저장하면 안 된다. 나진 발주서 실측 —
printerSettings1~3.bin·sharedStrings.xml·calcChain.xml· sheet rels 3개가 사라지고 126,679 → 102,971바이트. 발주서는 인쇄해 팩스로 나가는 문서라 프린터 설정 손실이 곧 사고다. ⛒zip 안 대상 시트 XML 한 장만 치환 → 실측 파트 손실 0. - openpyxl 의
cell.style_id는 XML 의s=와 다른 체계다. 그대로 쓰면 저장된 파일이IndexError로 안 열린다. → 스타일도 XML 에서 직접 읽는다(_xml_style_of). 「긴급」이 있던 칸은 날짜 서식이 아니라, 같은 열의 날짜 셀 서식을 베껴야46285로 안 보인다. ssm_run.py의 입력 JSON 파일명이 고정이라 «남의 스크립트가 실행됐다». 사이드카가 20초마다 같은 경로를 쓴다 — 내 E2E 스크립트를 보냈는데RELAY_CLAIM none이 돌아왔다. 청크 파일명만 고유화하고 입력 JSON 을 놓쳤던 것. →_ssm_input_<ms>_<pid>.json+ 실행 후 삭제.
화면 (web-designer 처방 반영)
- 🔑기존
.op-datewarn은{% raw %}{% if not omc_view %}{% endraw %}안에 갇혀 자재관리 두 화면엔 렌더조차 안 된다 — 확장이 아니라 신설이 맞았다. - 위치 = 세 라우트 공통 지점(
#opNewBanner직전). 접힘 기본 + 배지(🔴숨김 유발 / ⚠날짜 없음), 펼치면 2그룹 표. - 날짜 입력 = PFS 전역 패턴 그대로 네이티브
<input type="date">+showPicker()(droplist 규칙은 목록·자동완성 한정이라 저촉 없음). ⛒기존dateChip/opEditDate는.op-site컨텍스트에 묶여 있어 건드리지 않고 새 함수를 만들었다. - 폴러는 화면에 하나(55행 × setInterval 금지), 진행 건이 없으면 스스로 멈춘다. 모바일
order는 정수로 밀었다(dfx 4 · datewarn 5 · chips 6 · list 7). - 13px 하한 — 옛
.op-datewarn(11~12px)은 그 자체가 위반이라 베끼지 않았다.
동작 규칙
- 화면 저장 →
orderpaper_date_fix큐 1행 → 미니PC 사이드카가 20초 폴링으로 집어 NAS 엑셀을 고친다(EC2 는 NAS 를 못 본다). - 엑셀이 열려 있으면
waiting_file— 실패가 아니다. 닫힐 때까지 폴링마다 재시도하고 화면엔 「⚠ 엑셀 파일이 열려 있습니다 — 닫으면 자동 반영됩니다」. 1시간 넘으면 파일명을 텔레그램으로 1회(notified로 중복 차단). - 쓰기 전 자동 백업(원본 옆
_백업폴더), 쓴 뒤 다시 읽어 검증하고 어긋나면 백업으로 원복. - 성공하면
orderpaper행의 날짜도 즉시 갱신 — 다음 동기화(최대 30분)를 안 기다린다(엑셀이 이미 그 값이라 멱등). - 외부 업체 계정에는 목록을 빈 배열로 준다(엑셀 원본을 고치는 기능이라 자사·총관리자만).
검증
- 엑셀 쓰기 자체검사(
orderpaper_date_write.py사본 실행) — zip 파트 손실 0 · I3 「긴급」→2026-09-30 · 항목 43 유지 · 백업 생성. - 날짜 문제 블록 DB 집계 = 42건(오기입 14 · 날짜없음 28). 파서 경고(55)와 숫자가 다른 이유 = 파서는 파일 월 기준·블록(엣지 분리) 단위,
DB 는 등록일 기준·섹션 단위라 화면
badDate와 같은 잣대다(화면에서 실제로 숨는 것과 어긋나지 않게 DB 기준을 택했다). - E2E(테스트) — ①없는 파일 →
error+ 사람이 읽는 메시지 ②~$잠금파일 →waiting_file+wait_since기록 ③잠금 해제 → 실제 되쓰기. ⛒③은 업무 데이터가 안 바뀌게 「현재 발주일과 같은 값(2026-09-15)」으로 돌렸다 — 무슨 날짜를 넣을지는 페페가 화면에서 정할 일이다. - 테스트 배포 = master
7067a28→/api/health{"env":"test","ok":true}·/field/orderpaper200.
남은 것
- ⏳[페페] 육안·실사용 1회 —
https://pfs-test.poin.co.kr/field/orderpaper·https://pfs-test.poin.co.kr/omc/material상단 「⚠ 발주서 날짜 점검」 패널 → 펼쳐서 한 건 고쳐 보기(엑셀에 실제로 들어가는지). - ⏳[페페] 판단 — 확인되면 운영 배포할지. 지금은 테스트만 나가 있다(운영은 API 가 없어 사이드카가 401 을 받고 조용히 넘긴다).
- 1부의 남은 것 = 날짜 미기입 블록 정리 여부 → 이 패널이 그 도구다.
- 미적용(지시 범위 밖) — 표 중간 행 삽입 시 상태가 밀리는 문제(신분증을 품명+규격 기반으로 바꿔야 하는 큰 변경).