@simplysm/sd-claude 14.0.78 → 14.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/output-styles/sd-tone.md +26 -2
- package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
- package/claude/rules/sd-base-rules.md +109 -87
- package/claude/skills/sd-dev/SKILL.md +1 -1
- package/claude/skills/sd-impl/SKILL.md +15 -14
- package/claude/skills/sd-impl/references/spec-cross-check.md +2 -2
- package/claude/skills/sd-spec/SKILL.md +746 -192
- package/claude/skills/sd-spec/references/example-spec.md +107 -35
- package/claude/skills/sd-unpack/SKILL.md +39 -14
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/_common.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/eml_handler.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/pdf_handler.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/_common.py +59 -0
- package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +7 -0
- package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +11 -0
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +288 -79
- package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +3 -2
- package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +78 -10
- package/package.json +1 -1
- package/claude/skills/sd-spec/references/spec-authoring.md +0 -298
- package/claude/skills/sd-spec/references/spec-md-template.md +0 -29
- package/claude/skills/sd-wip/SKILL.md +0 -38
- package/claude/skills/sd-wip/evals/fixtures/empty/.gitkeep +0 -0
- package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/_wip.md +0 -3
- package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/spec.md +0 -15
- package/claude/skills/sd-wip/evals/fixtures/with-existing-wip/.wips/260101120000_acct.md +0 -6
- package/claude/skills/sd-wip/evals/fixtures/with-existing-wip-for-compact/.wips/260101120000_acct.md +0 -14
- package/claude/skills/sd-wip/evals/golden.jsonl +0 -4
- package/claude/skills/sd-wip/references/compact.md +0 -79
|
@@ -86,6 +86,12 @@ flowchart LR
|
|
|
86
86
|
|
|
87
87
|
관련 섹션: [화면.재고 확인], [자동 처리.재고 스냅샷]
|
|
88
88
|
|
|
89
|
+
### 3.2 마스터 데이터 변경 이력 [확정: 2026-04-01]
|
|
90
|
+
|
|
91
|
+
모든 마스터 데이터의 변경 (등록·편집·활성/비활성 전환) 이력을 자동 기록해야 함. 변경 추적·감사 목적.
|
|
92
|
+
|
|
93
|
+
관련 섹션: [횡단 처리.마스터 데이터 변경 이력]
|
|
94
|
+
|
|
89
95
|
## 4. 화면
|
|
90
96
|
|
|
91
97
|
| § | 분류 | 화면 | 유형 | 장치 |
|
|
@@ -415,21 +421,67 @@ flowchart LR
|
|
|
415
421
|
|
|
416
422
|
관련 섹션: [기타.과거 재고 조회], [화면.재고 확인]
|
|
417
423
|
|
|
418
|
-
## 6.
|
|
424
|
+
## 6. 횡단 처리
|
|
425
|
+
|
|
426
|
+
### 6.1 마스터 데이터 변경 이력 [확정: 2026-04-01]
|
|
427
|
+
|
|
428
|
+
```mermaid
|
|
429
|
+
flowchart LR
|
|
430
|
+
S([마스터 엔티티 insert/update/delete]) --> T1[변경 전·후 값 캡처]
|
|
431
|
+
T1 --> T2[DataLog 테이블에 한 행 적재]
|
|
432
|
+
T2 --> E1([완료])
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
목적:
|
|
436
|
+
|
|
437
|
+
- 마스터 변경 추적·감사 (누가 언제 무엇을 어떻게 바꿨는지)
|
|
438
|
+
|
|
439
|
+
트리거 (부수효과 발동 조건):
|
|
440
|
+
|
|
441
|
+
- 마스터 엔티티의 insert / update / delete 트랜잭션 commit 시
|
|
442
|
+
- 적용 범위: [모델.품목], [모델.Location]
|
|
443
|
+
- 적용 제외: 트랜잭션 모델 ([모델.재고], [모델.재고 스냅샷])
|
|
444
|
+
|
|
445
|
+
처리:
|
|
446
|
+
|
|
447
|
+
- 변경 전 값과 변경 후 값 캡처 (insert 시 변경 전 = null, delete 시 변경 후 = null)
|
|
448
|
+
- [모델.DataLog] 에 한 행 적재 (테이블·키·전·후·작업자·시각)
|
|
449
|
+
|
|
450
|
+
예외 처리:
|
|
451
|
+
|
|
452
|
+
- DataLog 적재 실패 시:
|
|
453
|
+
- 위험: 변경 추적 누락
|
|
454
|
+
- 대처: 본 트랜잭션은 정상 commit 유지, DataLog 적재 실패만 별도 큐로 적재 후 관리자 알림
|
|
455
|
+
- 재시도 한계: 큐에서 5회 지수 백오프 후 운영팀 알림
|
|
419
456
|
|
|
420
|
-
|
|
457
|
+
모델 매핑:
|
|
458
|
+
|
|
459
|
+
| 필드 ([모델.DataLog]) | 용도 |
|
|
460
|
+
| --------------------- | ------------------------------- |
|
|
461
|
+
| [모델.DataLog.테이블명] | 변경 대상 모델명 (예: `품목`) |
|
|
462
|
+
| [모델.DataLog.키] | 변경 대상 ID |
|
|
463
|
+
| [모델.DataLog.변경 전] | 컬럼별 변경 전 값 (JSON) |
|
|
464
|
+
| [모델.DataLog.변경 후] | 컬럼별 변경 후 값 (JSON) |
|
|
465
|
+
| [모델.DataLog.작업자] | 세션 사용자 |
|
|
466
|
+
| [모델.DataLog.시각] | 변경 commit 시각 |
|
|
467
|
+
|
|
468
|
+
관련 섹션: [기타.마스터 데이터 변경 이력], [모델.품목], [모델.Location], [모델.DataLog]
|
|
469
|
+
|
|
470
|
+
## 7. 공통 정의
|
|
471
|
+
|
|
472
|
+
### 7.1 용어 사전 [확정: 2026-04-01]
|
|
421
473
|
|
|
422
474
|
- 박스: 외부에서 이미 바코드(품목+수량+박스번호)가 부착된 입고 단위
|
|
423
475
|
- Location: 창고 내 적치 위치 ([공통 정의.Location 라벨])
|
|
424
476
|
- ERP 통보: 입출고 발생 시 WMS → ERP 단방향 알림
|
|
425
477
|
- 활성 여부: 마스터 데이터의 소프트 삭제 플래그 (true = 정상, false = 삭제 처리됨). UI 의 "삭제/복구" 액션과 매핑. 마스터에 완전 삭제(DB 레코드 제거) 없음.
|
|
426
478
|
|
|
427
|
-
###
|
|
479
|
+
### 7.2 박스 바코드 형식 [OPEN: 2026-04-01]
|
|
428
480
|
|
|
429
481
|
- 자료 위치: 첨부B.pdf p.5 "박스 코드 체계" — 외부 송장 시스템 규격 명세
|
|
430
|
-
- 자료 연결 메모: 박스 코드는 외부 규격이지만 [모델.박스] 식별 키 구조와 직결 — §
|
|
482
|
+
- 자료 연결 메모: 박스 코드는 외부 규격이지만 [모델.박스] 식별 키 구조와 직결 — §8 박스 확정과 함께 검토
|
|
431
483
|
|
|
432
|
-
###
|
|
484
|
+
### 7.3 Location 라벨 [확정: 2026-04-01]
|
|
433
485
|
|
|
434
486
|
```
|
|
435
487
|
┌────────────────────────────────┐
|
|
@@ -446,7 +498,7 @@ flowchart LR
|
|
|
446
498
|
- 라벨 크기: 100 × 50 mm
|
|
447
499
|
- 라벨 재질: 유포지, 흰색 무지
|
|
448
500
|
|
|
449
|
-
###
|
|
501
|
+
### 7.4 화주 품목 자료 [확정: 2026-04-01]
|
|
450
502
|
|
|
451
503
|
화주(품목을 본 WMS 에 보관 위탁한 거래처)가 자기 ERP 에서 수시로 다운로드해 화주별 별도 파일로 전달한다. 신규 화주 등록 또는 화주 측 품목 마스터 변경 시점에 본 WMS 에 1회 일괄 업로드한다. 식별 키는 `화주코드 + SEQ` 2컬럼 합성.
|
|
452
504
|
|
|
@@ -457,18 +509,18 @@ flowchart LR
|
|
|
457
509
|
| 품목명 | O | 화주 측 명칭 |
|
|
458
510
|
| 단위 | X | `EA` / `BOX` 등 |
|
|
459
511
|
|
|
460
|
-
##
|
|
512
|
+
## 8. 도메인 모델
|
|
461
513
|
|
|
462
|
-
###
|
|
514
|
+
### 8.1 품목 [확정: 2026-04-01]
|
|
463
515
|
|
|
464
516
|
필드:
|
|
465
517
|
|
|
466
|
-
| 필드 | 타입 | 필수 | 비고
|
|
467
|
-
| --------- | ------- | ---- |
|
|
468
|
-
| ID | 숫자 | O | 자동 부여
|
|
518
|
+
| 필드 | 타입 | 필수 | 비고 |
|
|
519
|
+
| --------- | ------- | ---- | -------------------- |
|
|
520
|
+
| ID | 숫자 | O | 자동 부여 |
|
|
469
521
|
| 코드 | 문자 | O | 자유 형식, 수정 가능 |
|
|
470
|
-
| 명칭 | 문자 | O |
|
|
471
|
-
| 활성 여부 | boolean | O |
|
|
522
|
+
| 명칭 | 문자 | O | |
|
|
523
|
+
| 활성 여부 | boolean | O | |
|
|
472
524
|
|
|
473
525
|
키/제약:
|
|
474
526
|
|
|
@@ -476,7 +528,7 @@ flowchart LR
|
|
|
476
528
|
- 비즈니스 키: 코드 (사용자 부여, 수정 가능)
|
|
477
529
|
- 유일성: 활성 품목 내 코드, 명칭 각각 유일
|
|
478
530
|
|
|
479
|
-
###
|
|
531
|
+
### 8.2 박스 [확정: 2026-04-01]
|
|
480
532
|
|
|
481
533
|
필드:
|
|
482
534
|
|
|
@@ -494,7 +546,7 @@ flowchart LR
|
|
|
494
546
|
- 박스 1개 = 단일 품목
|
|
495
547
|
- 유일성: 박스번호 유일
|
|
496
548
|
|
|
497
|
-
###
|
|
549
|
+
### 8.3 Location [확정: 2026-04-01]
|
|
498
550
|
|
|
499
551
|
필드:
|
|
500
552
|
|
|
@@ -510,7 +562,7 @@ flowchart LR
|
|
|
510
562
|
- 비즈니스 키: 코드 (사용자 부여, 수정 가능)
|
|
511
563
|
- 유일성: 활성 Location 내 코드 유일
|
|
512
564
|
|
|
513
|
-
###
|
|
565
|
+
### 8.4 재고 [확정: 2026-04-01]
|
|
514
566
|
|
|
515
567
|
필드:
|
|
516
568
|
|
|
@@ -526,25 +578,44 @@ flowchart LR
|
|
|
526
578
|
- 박스 1개는 동시에 1개 Location 에만 존재 (= 박스-재고 1:1)
|
|
527
579
|
- Location 1개에 박스 다중 가능
|
|
528
580
|
|
|
529
|
-
###
|
|
581
|
+
### 8.5 재고 스냅샷 [확정: 2026-04-01]
|
|
530
582
|
|
|
531
583
|
필드:
|
|
532
584
|
|
|
533
|
-
| 필드 | 타입 | 필수 | 비고
|
|
534
|
-
| -------- | --------------- | ---- |
|
|
535
|
-
| ID | 숫자 | O | 자동 부여
|
|
585
|
+
| 필드 | 타입 | 필수 | 비고 |
|
|
586
|
+
| -------- | --------------- | ---- | ----------- |
|
|
587
|
+
| ID | 숫자 | O | 자동 부여 |
|
|
536
588
|
| 날짜 | 날짜 | O | 스냅샷 일자 |
|
|
537
|
-
| 박스 | 참조 - 박스 | O |
|
|
538
|
-
| Location | 참조 - Location | O |
|
|
589
|
+
| 박스 | 참조 - 박스 | O | |
|
|
590
|
+
| Location | 참조 - Location | O | |
|
|
539
591
|
|
|
540
592
|
키/제약:
|
|
541
593
|
|
|
542
594
|
- 식별 키: ID (자동 부여)
|
|
543
595
|
- 비즈니스 키: (날짜, 박스) 조합 유일
|
|
544
596
|
|
|
545
|
-
|
|
597
|
+
### 8.6 DataLog [확정: 2026-04-01]
|
|
546
598
|
|
|
547
|
-
|
|
599
|
+
필드:
|
|
600
|
+
|
|
601
|
+
| 필드 | 타입 | 필수 | 비고 |
|
|
602
|
+
| -------- | ---- | ---- | ------------------------------------- |
|
|
603
|
+
| ID | 숫자 | O | 자동 부여 |
|
|
604
|
+
| 테이블명 | 문자 | O | 변경 대상 모델명 (예: `품목`) |
|
|
605
|
+
| 키 | 숫자 | O | 변경 대상 ID |
|
|
606
|
+
| 변경 전 | JSON | X | 컬럼별 이전 값. insert 시 null |
|
|
607
|
+
| 변경 후 | JSON | X | 컬럼별 변경 후 값. delete 시 null |
|
|
608
|
+
| 작업자 | 문자 | O | 세션 사용자 |
|
|
609
|
+
| 시각 | 일시 | O | 변경 commit 시각 |
|
|
610
|
+
|
|
611
|
+
키/제약:
|
|
612
|
+
|
|
613
|
+
- 식별 키: ID (자동 부여)
|
|
614
|
+
- 인덱스: (테이블명, 키, 시각) — 특정 엔티티 변경 이력 조회용
|
|
615
|
+
|
|
616
|
+
## 9. 외부 인터페이스
|
|
617
|
+
|
|
618
|
+
### 9.1 ERP 입고 통보 [확정: 2026-04-01]
|
|
548
619
|
|
|
549
620
|
- 상대 시스템: ERP
|
|
550
621
|
- 방향: WMS → ERP
|
|
@@ -569,21 +640,22 @@ flowchart LR
|
|
|
569
640
|
|
|
570
641
|
관련 섹션: [화면.입고 스캔]
|
|
571
642
|
|
|
572
|
-
##
|
|
643
|
+
## 10. 본문 외 결정사항
|
|
573
644
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
- 직원 관리 사원번호 컬럼 추가
|
|
645
|
+
- 2026-04-01 [제외]: 직원 관리 사원번호 컬럼 추가
|
|
646
|
+
- 근거: 본 spec 범위 밖 — 인사 마스터 영역
|
|
577
647
|
- 후속 처리: sd-dev
|
|
578
648
|
- 자료 위치: 회의록.md L42
|
|
579
|
-
|
|
649
|
+
|
|
650
|
+
- 2026-04-01 [제외]: 재고 확인 화면 정렬 버그
|
|
651
|
+
- 근거: 본 spec 범위 밖 — 운영 잔손
|
|
580
652
|
- 후속 처리: 운영 잔손
|
|
581
653
|
- 자료 위치: 이슈 #128
|
|
582
654
|
|
|
583
|
-
|
|
655
|
+
- 2026-04-01: 출고 분석은 입고 끝나고 다음 단계
|
|
656
|
+
- 근거: 사용자 결정 — 입고 검증 후 출고 진행이 안전
|
|
657
|
+
- 자료 위치: 회의록.md L40-50
|
|
658
|
+
- 영향: §2.2 출고 OPEN 상태 유지
|
|
584
659
|
|
|
585
|
-
- 2026-04-
|
|
586
|
-
-
|
|
587
|
-
- 2026-04-01: 과거 재고 조회 요구 식별 → 재고 스냅샷 모델·자동 처리 추가, 재고 확인 화면에 기준일 필터 반영.
|
|
588
|
-
- 2026-04-01: ERP 입고 통보 식별 → 입고 스캔 등록 후속 동작에서 호출.
|
|
589
|
-
- 2026-04-05: 품목 관리·품목 등록·편집 sd-impl 완료 (구현 마커 부착).
|
|
660
|
+
- 2026-04-05: 품목 마스터 sd-impl 완료 (구현 마커 부착)
|
|
661
|
+
- 근거: 시연 검증 통과 (§4.1·§4.2)
|
|
@@ -49,10 +49,10 @@ meeting_eml/
|
|
|
49
49
|
embedded_xlsx/
|
|
50
50
|
README.md
|
|
51
51
|
_source.xlsx
|
|
52
|
-
workbook.meta.json ← defined names
|
|
52
|
+
workbook.meta.json ← defined names·sheet_code_map (있을 때)
|
|
53
53
|
sheets/
|
|
54
|
-
01_Sheet1.png
|
|
55
|
-
01_Sheet1.jsonl
|
|
54
|
+
01_Sheet1.png ← 시각 (서식·바탕색·border 모두)
|
|
55
|
+
01_Sheet1.jsonl ← 분석 데이터 (값·number_format·수식·merges 등)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
형식별 산출물 매트릭스:
|
|
@@ -61,7 +61,7 @@ meeting_eml/
|
|
|
61
61
|
|---|---|---|---|
|
|
62
62
|
| pptx/ppt | `slides/<idx>_<title>.png` | `slides/<idx>_<title>.jsonl` (슬라이드별 노드) | `charts/*.data.json`, `images/`, `attachments/`, `macros/` |
|
|
63
63
|
| docx/doc | `pages/<NNN>.png` (시각 검증용) | `content.jsonl` (단일 시퀀스), `pages.meta.json` (PNG↔노드 매핑) | `images/`, `attachments/`, `macros/` |
|
|
64
|
-
| xlsx/xlsb/xls | `sheets/<idx>_<name>.png` | `sheets/<idx>_<name>.jsonl` (값·수식·시트 메타
|
|
64
|
+
| xlsx/xlsb/xls | `sheets/<idx>_<name>.png` (일반 시트만) | `sheets/<idx>_<name>.jsonl` (값·수식·시트 메타), `workbook.meta.json` | `charts/sheet<idx>_chart*.data.json` (일반 시트 안 차트 + Chartsheet 의 차트), `images/<sheet>_<cell>`, `attachments/`, `macros/` |
|
|
65
65
|
| pdf | `pages/<NNN>.png` | `pages/<NNN>.jsonl` (블록 bbox + 표 셀 단위) | `images/p<NNN>_b<bid>.<ext>`, `attachments/` (PDF 임베드) |
|
|
66
66
|
| eml/msg | — | `body.md` (평문 본문), `headers.json`, `images.rels.json` | `body.html` (원본), `attachments/` (컨테이너면 재귀) |
|
|
67
67
|
|
|
@@ -78,11 +78,14 @@ meeting_eml/
|
|
|
78
78
|
|
|
79
79
|
## xlsx jsonl 규약
|
|
80
80
|
|
|
81
|
-
시트별 `.jsonl
|
|
81
|
+
시트별 `.jsonl` — 분석 핵심 (값·number_format·수식·merges·hyperlinks·comments). 시각 표시 (바탕색·border·폰트)·frozen·dims 는 미보존 (PNG 가 시각 보조, 필요 시 `_source.xlsx` 직접 추출).
|
|
82
82
|
|
|
83
|
-
- 첫 줄: `{"_meta":{"
|
|
84
|
-
-
|
|
85
|
-
- `number_formats`: General(기본) 외
|
|
83
|
+
- 첫 줄: `{"_meta":{"merges":["A1:C1",...], "number_formats":{"E1":"yyyy-mm-dd",...}, "hyperlinks":{"D5":"http://..."}, "comments":{"E3":"메모"}}}`
|
|
84
|
+
- `merges`: 머지된 셀 영역 (셀 좌표 해석에 필수 — 머지 영역 안 빈 셀 오해 차단)
|
|
85
|
+
- `number_formats`: General(기본) 외 셀 표시 형식 — Date·통화·% 등 셀 값 의미 단서
|
|
86
|
+
- `hyperlinks`: 셀 URL (URL 자체가 셀 정보)
|
|
87
|
+
- `comments`: 셀 메모
|
|
88
|
+
- 비어있는 메타 키는 생략 (모두 비면 `{"_meta":{}}`)
|
|
86
89
|
- 데이터 줄: `{"r":11, "A":"P001", "I":7800, "J":12.5, "_f":{"I":"=SUM(...)", "J":"=I11*1.5"}}`
|
|
87
90
|
- `r`: 1-based 행번호 (Excel 동일)
|
|
88
91
|
- 열문자 키 (`A`·`B`·...·`AA`·...): 셀 값. 빈 셀은 키 생략
|
|
@@ -90,8 +93,22 @@ meeting_eml/
|
|
|
90
93
|
- 빈 행도 `{"r":N}` 한 줄 유지 → Read offset = 행번호 (오프바이원 차단)
|
|
91
94
|
- 값 타입: JSON 네이티브 (`int`·`float`·`bool`·`str`), datetime 은 ISO 8601 문자열
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
### Chartsheet (시트 자체가 차트)
|
|
97
|
+
|
|
98
|
+
xlsx 안 시트는 일반 Worksheet 외에 **Chartsheet** (셀 없이 차트 1개) 도 있을 수 있음.
|
|
99
|
+
|
|
100
|
+
- Chartsheet 는 `sheets/<idx>_<name>.jsonl` 미생성 (셀 없음)
|
|
101
|
+
- Chartsheet 의 차트 데이터: `charts/sheet<idx>_chart.data.json`
|
|
102
|
+
- README sheet_summaries 에 `(chart sheet — "...")` 명시
|
|
103
|
+
- 일반 시트·Chartsheet 통합 시트 순서 (idx) 대로 보존
|
|
104
|
+
|
|
105
|
+
### 워크북 단위 `workbook.meta.json`
|
|
106
|
+
|
|
107
|
+
시트 외 워크북 공통 정보 (있을 때만 생성):
|
|
108
|
+
|
|
94
109
|
- `defined_names`: `{"이름":["'Sheet1'!$A$1:$C$10", ...]}` (다중 destination 시 list 다수 항목)
|
|
110
|
+
- `sheet_code_map`: `{"Sheet1":"BOA", ...}` (VBA codeName → raw 시트명. 매크로 모듈 파일명과 매칭용)
|
|
111
|
+
- `pivots`: pivot table 정의 list. 각 항목 `{name, source, location, rowFields, colFields, pageFields, dataFields}`. 결과 셀은 시트별 jsonl 에 일반 셀로 들어감
|
|
95
112
|
|
|
96
113
|
## pptx jsonl 규약
|
|
97
114
|
|
|
@@ -146,15 +163,17 @@ paragraph 안 hyperlink 가 있으면 `hyperlinks`: `[{"text":"...", "url":"..."
|
|
|
146
163
|
|
|
147
164
|
페이지별 `pages/<NNN>.jsonl`. PDF 페이지는 원본 단위.
|
|
148
165
|
|
|
149
|
-
- 첫 줄: `{"_meta":{"page":N, "size":[w,h], "blocks":B, "tables":T, "table_cells":C}}`
|
|
166
|
+
- 첫 줄: `{"_meta":{"page":N, "size":[w,h], "blocks":B, "tables":T, "table_cells":C, "form_fields":F, "annotations":A}}`
|
|
150
167
|
- 노드 줄:
|
|
151
168
|
- `text_block`: `{"page":N, "block":B, "type":"text_block", "bbox":[x0,y0,x1,y1], "text":"..."}`
|
|
152
169
|
- `image_block`: `{"page":N, "block":B, "type":"image_block", "bbox":[...], "ref":"images/p001_b03.png"}`
|
|
153
170
|
- `table_cell`: `{"page":N, "type":"table_cell", "table_idx":T, "table_bbox":[...], "row":R, "col":C, "text":"..."}`
|
|
171
|
+
- `form_field`: `{"page":N, "type":"form_field", "name":"...", "field_type":"text", "value":"...", "bbox":[...]}` (PDF 양식 입력란)
|
|
172
|
+
- `annotation`: `{"page":N, "type":"annotation", "subtype":"Highlight", "bbox":[...], "content":"...", "author":"..."}` (주석·highlight·sticky note)
|
|
154
173
|
- 모든 블록 보존 (표 영역과 겹쳐도 skip 안 함) — find_tables 정확도 100% 가정 시 정보 손실 위험 회피. text_block·image_block·table_cell 노드가 동일 영역에 중복 출력될 수 있음. Claude 가 양쪽 비교 판단
|
|
155
|
-
- bbox 는 PDF 기준 좌표 (left-top, pt 단위,
|
|
174
|
+
- bbox 는 PDF 기준 좌표 (left-top, pt 단위, raw float)
|
|
156
175
|
|
|
157
|
-
heading 추출은 미적용 (PDF 는 style 정보 없음).
|
|
176
|
+
heading 추출은 미적용 (PDF 는 style 정보 없음). OCR 미적용 (스캔 PDF 는 image_block 만 추출).
|
|
158
177
|
|
|
159
178
|
## 인라인 이미지 매핑 (eml/msg)
|
|
160
179
|
|
|
@@ -166,6 +185,12 @@ heading 추출은 미적용 (PDF 는 style 정보 없음). 필요 시 본문 gre
|
|
|
166
185
|
- text/plain·HTML 둘 다 있을 때 → `body.md` 는 plain (placeholder 없음), `body.from_html.md` 가 변환본 (placeholder 포함)
|
|
167
186
|
- 인라인 이미지 없으면 `images.rels.json` 미생성
|
|
168
187
|
|
|
188
|
+
## TNEF (winmail.dat) 풀이
|
|
189
|
+
|
|
190
|
+
Outlook RTF 메일이 첨부를 `winmail.dat` 단일 binary (TNEF 형식) 로 패키징한 경우, `tnefparse` 로 내부 첨부 추출하여 `attachments/` 에 같이 풀어 둠. 원본 `winmail.dat` 도 유지 (원본 보존).
|
|
191
|
+
|
|
192
|
+
내부 첨부도 컨테이너 (xlsx·pptx 등) 면 재귀 풀이 (다른 첨부와 동일).
|
|
193
|
+
|
|
169
194
|
## eml/msg 본문 규약
|
|
170
195
|
|
|
171
196
|
본문 흐름 정확성(text/plain 우선) + 인라인 이미지 위치 단서(HTML→평문 변환본) 둘 다 보존:
|
|
@@ -183,8 +208,8 @@ heading 추출은 미적용 (PDF 는 style 정보 없음). 필요 시 본문 gre
|
|
|
183
208
|
## xlsb 클린업
|
|
184
209
|
|
|
185
210
|
- legacy → xlsx 변환 시 `_converted.xlsx` 는 임시 폴더에서만 처리 (산출 폴더에 미잔존)
|
|
186
|
-
- VBA
|
|
187
|
-
|
|
211
|
+
- VBA 매크로는 원본 코드 그대로 `macros/<모듈명>.vba` 저장 (변형 X)
|
|
212
|
+
- VBA 시트 객체명↔raw 시트명 매핑은 `workbook.meta.json` 의 `sheet_code_map` 키 (예: `{"Sheet1":"BOA","Sheet3":"Mapping"}`)
|
|
188
213
|
|
|
189
214
|
## 산출물 사용
|
|
190
215
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -160,6 +160,65 @@ def temp_workdir():
|
|
|
160
160
|
shutil.rmtree(d, ignore_errors=True)
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
def is_tnef(path: Path) -> bool:
|
|
164
|
+
"""TNEF (winmail.dat) 형식인지 검사. filename 또는 magic bytes."""
|
|
165
|
+
if path.name.lower() in ("winmail.dat", "win.dat"):
|
|
166
|
+
return True
|
|
167
|
+
try:
|
|
168
|
+
with open(long_str(path), "rb") as f:
|
|
169
|
+
magic = f.read(4)
|
|
170
|
+
return magic == b"\x78\x9f\x3e\x22"
|
|
171
|
+
except Exception:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def unpack_tnef(path: Path, attachments_dir: Path) -> list[Path]:
|
|
176
|
+
"""TNEF (winmail.dat) 내부 첨부 추출. 추출된 path list 반환.
|
|
177
|
+
|
|
178
|
+
TNEF 아니거나 실패 시 빈 list. 원본 winmail.dat 은 유지 (원본 보존).
|
|
179
|
+
"""
|
|
180
|
+
if not is_tnef(path):
|
|
181
|
+
return []
|
|
182
|
+
ensure_pip("tnefparse")
|
|
183
|
+
try:
|
|
184
|
+
from tnefparse import TNEF
|
|
185
|
+
with open(long_str(path), "rb") as f:
|
|
186
|
+
t = TNEF(f.read())
|
|
187
|
+
except Exception:
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
saved: list[Path] = []
|
|
191
|
+
for att in getattr(t, "attachments", []):
|
|
192
|
+
try:
|
|
193
|
+
name = None
|
|
194
|
+
lf = getattr(att, "long_filename", None)
|
|
195
|
+
if callable(lf):
|
|
196
|
+
try:
|
|
197
|
+
name = lf()
|
|
198
|
+
except Exception:
|
|
199
|
+
name = None
|
|
200
|
+
if not name:
|
|
201
|
+
name = getattr(att, "name", None)
|
|
202
|
+
if isinstance(name, bytes):
|
|
203
|
+
try:
|
|
204
|
+
name = name.decode("utf-8")
|
|
205
|
+
except UnicodeDecodeError:
|
|
206
|
+
name = name.decode("cp949", errors="replace")
|
|
207
|
+
if not name:
|
|
208
|
+
name = "tnef_attachment.bin"
|
|
209
|
+
data = att.data
|
|
210
|
+
if not data:
|
|
211
|
+
continue
|
|
212
|
+
except Exception:
|
|
213
|
+
continue
|
|
214
|
+
if not isinstance(name, str):
|
|
215
|
+
name = "tnef_attachment.bin"
|
|
216
|
+
dst = unique_path(attachments_dir, name)
|
|
217
|
+
write_bytes(dst, data)
|
|
218
|
+
saved.append(dst)
|
|
219
|
+
return saved
|
|
220
|
+
|
|
221
|
+
|
|
163
222
|
def save_source(input_path: Path, out_dir: Path) -> None:
|
|
164
223
|
ext = input_path.suffix.lstrip(".")
|
|
165
224
|
dst = out_dir / f"_source.{ext}"
|
|
@@ -136,6 +136,13 @@ def run(input_path: Path, out_dir: Path) -> None:
|
|
|
136
136
|
json.dumps(rels, ensure_ascii=False, indent=2),
|
|
137
137
|
)
|
|
138
138
|
|
|
139
|
+
# TNEF (winmail.dat) 풀이 — Outlook RTF 메일의 첨부 패키지 안 내부 첨부 추출
|
|
140
|
+
tnef_saved: list[Path] = []
|
|
141
|
+
for ap in saved_attachments:
|
|
142
|
+
extra = _common.unpack_tnef(ap, attachments_dir)
|
|
143
|
+
tnef_saved.extend(extra)
|
|
144
|
+
saved_attachments.extend(tnef_saved)
|
|
145
|
+
|
|
139
146
|
attachment_links: list[str] = []
|
|
140
147
|
for ap in saved_attachments:
|
|
141
148
|
size = ap.stat().st_size
|
|
@@ -111,6 +111,17 @@ def run(input_path: Path, out_dir: Path) -> None:
|
|
|
111
111
|
size = dst.stat().st_size
|
|
112
112
|
if cid:
|
|
113
113
|
cid_map[cid] = dst.name
|
|
114
|
+
|
|
115
|
+
# TNEF (winmail.dat) 풀이 — Outlook 첨부 패키지 안 내부 첨부 추출
|
|
116
|
+
for tnef_ap in _common.unpack_tnef(dst, attachments_dir):
|
|
117
|
+
t_size = tnef_ap.stat().st_size
|
|
118
|
+
t_recursed = maybe_recurse_attachment(tnef_ap, attachments_dir)
|
|
119
|
+
if t_recursed is not None:
|
|
120
|
+
os.unlink(_common.long_str(tnef_ap))
|
|
121
|
+
attachment_links.append(f"attachments/{t_recursed.name}/ ({_common.format_size(t_size)})")
|
|
122
|
+
else:
|
|
123
|
+
attachment_links.append(f"attachments/{tnef_ap.name} ({_common.format_size(t_size)})")
|
|
124
|
+
|
|
114
125
|
recursed = maybe_recurse_attachment(dst, attachments_dir)
|
|
115
126
|
if recursed is not None:
|
|
116
127
|
os.unlink(_common.long_str(dst))
|