uspto-oa-cli 0.1.0__tar.gz

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.
@@ -0,0 +1,24 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # data
13
+ file/
14
+
15
+ # AI Coding Assistants configs & logs
16
+ .antigravitycli/
17
+ .claude/
18
+
19
+ # secrets
20
+ .env
21
+
22
+ # temp
23
+ temp/
24
+ test/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,84 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## 프로젝트 개요
6
+
7
+ USPTO ODP(Open Data Portal) API를 통해 미국 특허 심사과정 문서를 다운로드하고, XML 파싱을 거쳐 구조화된 Markdown으로 변환하는 CLI 도구. AI 에이전트(Claude Code, Gemini CLI)가 생성된 MD 파일을 읽고 심사 전략을 분석하는 워크플로우를 목표로 한다. LLM API 직접 호출은 최소화하고 AI 코딩 에이전트에게 분석을 위임하는 방식을 지향한다.
8
+
9
+ ## 실행 명령
10
+
11
+ ```bash
12
+ # 의존성 설치
13
+ uv sync
14
+
15
+ # API 키 설정 (최초 1회) → ~/.oa-cli.toml 에 저장
16
+ uv run uspto-oa configure
17
+
18
+ # 문서 다운로드
19
+ uv run uspto-oa download 16330077
20
+
21
+ # XML 파싱 → MD 생성 (file/{app_num}/{app_num}_prosecution.md)
22
+ uv run uspto-oa extract 16330077
23
+
24
+ # 특허번호 입력 (출원번호 자동 변환)
25
+ uv run uspto-oa download US12228644B2
26
+
27
+ # 특정 문서 코드만 다운로드
28
+ uv run uspto-oa download 16330077 --doc-codes CTNF,CTFR,NOA
29
+
30
+ # 강제 재다운로드
31
+ uv run uspto-oa download 16330077 --force
32
+
33
+ # 상세 로그
34
+ uv run uspto-oa -v download 16330077
35
+
36
+ # PyPI 배포 (빌드 → 업로드)
37
+ uv build && uv run twine upload dist/*
38
+ ```
39
+
40
+ **API 키 우선순위**: `--api-key` 옵션 > `USPTO_API_KEY` 환경변수 > `~/.oa-cli.toml` (개발 시 `.env` 자동 로드)
41
+
42
+ ## 소스 구조
43
+
44
+ ```
45
+ src/oa_cli/ # PyPI 배포 패키지 (hatchling wheel 타겟)
46
+ ├── __init__.py # __version__
47
+ ├── __main__.py # python -m oa_cli 지원
48
+ ├── cli.py # click 진입점 (configure / download / extract 커맨드)
49
+ ├── config.py # API 키 우선순위 관리, ~/.oa-cli.toml 읽기/쓰기
50
+ ├── uspto/ # USPTO ODP API 저수준 클라이언트 (도메인 무관)
51
+ │ ├── client.py # make_api_request() — GET + 재시도/백오프
52
+ │ ├── documents.py # get_application_documents() — 문서 목록 API
53
+ │ └── download.py # download_file() / download_bytes()
54
+ └── prosecution/ # 심사과정 분석 도메인 로직
55
+ ├── collect.py # 문서 필터링 + XML 우선 일괄 다운로드
56
+ └── extract.py # XML 파싱 → prosecution.md 생성
57
+ file/
58
+ └── {app_num}/ # 다운로드 파일 + 생성된 MD 저장 위치
59
+ ```
60
+
61
+ ## 아키텍처 핵심 사항
62
+
63
+ **레이어 분리**: `src/uspto/`는 API 통신만 담당(도메인 무관), `src/prosecution/`은 심사과정 비즈니스 로직 담당. `cli.py`는 두 레이어를 연결하는 진입점.
64
+
65
+ **서버사이드 필터 미사용**: USPTO API의 `documentCodes` 파라미터가 500 오류를 유발하므로, 전건 조회 후 클라이언트에서 필터링한다(`collect.py`의 `PROSECUTION_CODES_EXACT` / `PROSECUTION_CODE_PREFIXES`).
66
+
67
+ **파일명 규칙**: `{날짜}_{코드}_{문서ID}.{ext}` — 날짜 오름차순 정렬, 확장자는 실제 포맷 반영.
68
+
69
+ **다운로드 우선순위**: XML > PDF (MS_WORD 제외). XML은 tar 아카이브로 전달되므로 압축 해제 후 저장하며, tar 내 `.xml` 없거나 오류 시 PDF로 자동 폴백.
70
+
71
+ **XML 구조 (USPTO ST96 스키마)**: CTNF/CTFR는 `FormParagraph[07-21-aia]`에 거절 항목, NOA/NACT는 `FormParagraph[12-151-07]`에 허여 청구항, `FormParagraph[13-03]`에 Examiner's Statement 포함.
72
+
73
+ **PDF 특이사항**: USPTO OA PDF는 전 페이지 이미지 구성 → pypdf 텍스트 추출 불가. PDF 전용 문서(Amendment 등)는 MD에 경로만 참조로 포함하고 AI 에이전트가 필요 시 직접 읽도록 한다.
74
+
75
+ **생성 MD 구조** (`{app_num}_prosecution.md`):
76
+ - 타임라인 표 (전체 문서, XML/PDF 형식 표시)
77
+ - Office Action 상세 (CTNF/CTFR 거절 항목 전문)
78
+ - Notice of Allowance 상세 (허여 청구항 + Examiner's Statement)
79
+ - PDF 전용 문서 목록 (AI 에이전트 직접 전달용)
80
+
81
+ ## 다음 구현 단계
82
+
83
+ - `--step summary/detail/prior-art/strategy` — 생성된 prosecution.md를 AI 에이전트에 넘기는 분석 진입점 (LLM API 직접 호출 없이 Claude Code / Gemini CLI에 위임)
84
+ - `--no-download` — 이미 다운로드된 파일 기준으로 `--extract` 바로 수행
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: uspto-oa-cli
3
+ Version: 0.1.0
4
+ Summary: USPTO 특허 심사과정 분석 CLI — 문서 다운로드 · XML 파싱 · MD 생성
5
+ Project-URL: Homepage, https://github.com/noaa/uspto-oa-cli
6
+ License: MIT
7
+ Keywords: cli,office-action,patent,prosecution,uspto
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Legal Industry
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
16
+ Requires-Python: >=3.13
17
+ Requires-Dist: click>=8.0
18
+ Requires-Dist: pypdf>=6.11.0
19
+ Requires-Dist: requests>=2.34.2
20
+ Requires-Dist: rich>=15.0.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # odp-oa-cli
24
+
25
+ USPTO ODP(Open Data Portal) API를 통해 미국 특허 심사과정 문서를 다운로드하고, XML 파싱을 거쳐 구조화된 Markdown으로 변환하는 CLI 도구.
26
+
27
+ 생성된 MD 파일을 Claude Code, Gemini CLI 등 AI 에이전트에게 전달하여 심사 전략 분석을 수행하는 워크플로우를 지원한다.
28
+
29
+ ## 요구사항
30
+
31
+ - Python 3.13+
32
+ - [uv](https://docs.astral.sh/uv/)
33
+ - USPTO API 키 ([ODP 포털](https://developer.uspto.gov/)에서 발급)
34
+
35
+ ## 설치
36
+
37
+ ```bash
38
+ # 로컬 개발
39
+ uv sync
40
+
41
+ # PyPI에서 설치
42
+ pip install odp-oa-cli
43
+ ```
44
+
45
+ ## API 키 설정
46
+
47
+ ```bash
48
+ # 대화형 설정 (권장) — ~/.oa-cli.toml 에 저장
49
+ uspto-oa configure
50
+
51
+ # 현재 설정 확인
52
+ uspto-oa configure --show
53
+ ```
54
+
55
+ 또는 환경변수로 지정:
56
+
57
+ ```bash
58
+ export USPTO_API_KEY=your_api_key_here
59
+ ```
60
+
61
+ ## 사용법
62
+
63
+ ```bash
64
+ # 1. 문서 다운로드 (file/{app_num}/ 에 저장)
65
+ uspto-oa download 16330077
66
+
67
+ # 2. XML 파싱 → prosecution.md 생성
68
+ uspto-oa extract 16330077
69
+ # 결과: file/16330077/16330077_prosecution.md
70
+
71
+ # 특허번호 입력 (출원번호 자동 변환)
72
+ uspto-oa download US12228644B2
73
+
74
+ # 특정 문서 코드만 다운로드
75
+ uspto-oa download 16330077 --doc-codes CTNF,CTFR,NOA
76
+
77
+ # 강제 재다운로드 (기존 파일 덮어쓰기)
78
+ uspto-oa download 16330077 --force
79
+
80
+ # 상세 로그
81
+ uspto-oa -v download 16330077
82
+
83
+ # 일회성 API 키 지정
84
+ uspto-oa download 16330077 --api-key YOUR_KEY
85
+ ```
86
+
87
+ ### 커맨드 옵션
88
+
89
+ **`uspto-oa download <application>`**
90
+
91
+ | 옵션 | 설명 |
92
+ |------|------|
93
+ | `--doc-codes CODES` | 쉼표 구분 문서 코드 (예: `CTNF,CTFR,NOA`). 생략 시 전체 수집 대상 |
94
+ | `--output-dir DIR` | 저장 경로 (기본: `file/{app_num}/`) |
95
+ | `--force` | 기존 파일도 재다운로드 |
96
+ | `--api-key TEXT` | API 키 (설정 파일·환경변수보다 우선) |
97
+
98
+ **`uspto-oa extract <application>`**
99
+
100
+ | 옵션 | 설명 |
101
+ |------|------|
102
+ | `--output-dir DIR` | 파일 디렉토리 (기본: `file/{app_num}/`) |
103
+
104
+ ## 워크플로우
105
+
106
+ ```
107
+ uspto-oa download {app_num}
108
+ └─ file/{app_num}/ 에 XML / PDF 저장
109
+
110
+ uspto-oa extract {app_num}
111
+ └─ file/{app_num}/{app_num}_prosecution.md 생성
112
+ └─ AI 에이전트 (Claude Code / Gemini CLI)
113
+ └─ 심사 전략 분석, 요약, 질의응답
114
+ ```
115
+
116
+ ## 수집 대상 문서 코드
117
+
118
+ | 코드 | 의미 |
119
+ |------|------|
120
+ | `CTNF` | Non-Final Office Action |
121
+ | `CTFR` | Final Office Action |
122
+ | `NOA` / `NACT` | Notice of Allowance |
123
+ | `REM` | Remarks (의견서) |
124
+ | `ABN` | Abandonment |
125
+ | `SRNT` / `SRFW` | Search Report |
126
+ | `EXIN` | Examiner Interview |
127
+ | `RCE` / `RCEX` | Request for Continued Examination |
128
+ | `CTAV` | Advisory Action |
129
+ | `892` / `1449` / `IDS` | Prior Art / IDS |
130
+ | `A*` | Amendment 계열 전체 |
131
+
132
+ ## 생성 파일 구조
133
+
134
+ `file/{app_num}/{app_num}_prosecution.md`:
135
+
136
+ | 섹션 | 내용 |
137
+ |------|------|
138
+ | 타임라인 | 전체 문서 날짜순 표 (XML/PDF 형식 표시) |
139
+ | Office Action 상세 | CTNF/CTFR 거절 항목 전문 |
140
+ | Notice of Allowance 상세 | 허여 청구항 + Examiner's Statement |
141
+ | PDF 전용 문서 | Amendment 등 이미지 PDF 목록 (AI 에이전트 직접 전달용) |
142
+
143
+ ## PyPI 배포
144
+
145
+ ```bash
146
+ uv build
147
+ uv run twine upload dist/*
148
+ ```
@@ -0,0 +1,126 @@
1
+ # odp-oa-cli
2
+
3
+ USPTO ODP(Open Data Portal) API를 통해 미국 특허 심사과정 문서를 다운로드하고, XML 파싱을 거쳐 구조화된 Markdown으로 변환하는 CLI 도구.
4
+
5
+ 생성된 MD 파일을 Claude Code, Gemini CLI 등 AI 에이전트에게 전달하여 심사 전략 분석을 수행하는 워크플로우를 지원한다.
6
+
7
+ ## 요구사항
8
+
9
+ - Python 3.13+
10
+ - [uv](https://docs.astral.sh/uv/)
11
+ - USPTO API 키 ([ODP 포털](https://developer.uspto.gov/)에서 발급)
12
+
13
+ ## 설치
14
+
15
+ ```bash
16
+ # 로컬 개발
17
+ uv sync
18
+
19
+ # PyPI에서 설치
20
+ pip install odp-oa-cli
21
+ ```
22
+
23
+ ## API 키 설정
24
+
25
+ ```bash
26
+ # 대화형 설정 (권장) — ~/.oa-cli.toml 에 저장
27
+ uspto-oa configure
28
+
29
+ # 현재 설정 확인
30
+ uspto-oa configure --show
31
+ ```
32
+
33
+ 또는 환경변수로 지정:
34
+
35
+ ```bash
36
+ export USPTO_API_KEY=your_api_key_here
37
+ ```
38
+
39
+ ## 사용법
40
+
41
+ ```bash
42
+ # 1. 문서 다운로드 (file/{app_num}/ 에 저장)
43
+ uspto-oa download 16330077
44
+
45
+ # 2. XML 파싱 → prosecution.md 생성
46
+ uspto-oa extract 16330077
47
+ # 결과: file/16330077/16330077_prosecution.md
48
+
49
+ # 특허번호 입력 (출원번호 자동 변환)
50
+ uspto-oa download US12228644B2
51
+
52
+ # 특정 문서 코드만 다운로드
53
+ uspto-oa download 16330077 --doc-codes CTNF,CTFR,NOA
54
+
55
+ # 강제 재다운로드 (기존 파일 덮어쓰기)
56
+ uspto-oa download 16330077 --force
57
+
58
+ # 상세 로그
59
+ uspto-oa -v download 16330077
60
+
61
+ # 일회성 API 키 지정
62
+ uspto-oa download 16330077 --api-key YOUR_KEY
63
+ ```
64
+
65
+ ### 커맨드 옵션
66
+
67
+ **`uspto-oa download <application>`**
68
+
69
+ | 옵션 | 설명 |
70
+ |------|------|
71
+ | `--doc-codes CODES` | 쉼표 구분 문서 코드 (예: `CTNF,CTFR,NOA`). 생략 시 전체 수집 대상 |
72
+ | `--output-dir DIR` | 저장 경로 (기본: `file/{app_num}/`) |
73
+ | `--force` | 기존 파일도 재다운로드 |
74
+ | `--api-key TEXT` | API 키 (설정 파일·환경변수보다 우선) |
75
+
76
+ **`uspto-oa extract <application>`**
77
+
78
+ | 옵션 | 설명 |
79
+ |------|------|
80
+ | `--output-dir DIR` | 파일 디렉토리 (기본: `file/{app_num}/`) |
81
+
82
+ ## 워크플로우
83
+
84
+ ```
85
+ uspto-oa download {app_num}
86
+ └─ file/{app_num}/ 에 XML / PDF 저장
87
+
88
+ uspto-oa extract {app_num}
89
+ └─ file/{app_num}/{app_num}_prosecution.md 생성
90
+ └─ AI 에이전트 (Claude Code / Gemini CLI)
91
+ └─ 심사 전략 분석, 요약, 질의응답
92
+ ```
93
+
94
+ ## 수집 대상 문서 코드
95
+
96
+ | 코드 | 의미 |
97
+ |------|------|
98
+ | `CTNF` | Non-Final Office Action |
99
+ | `CTFR` | Final Office Action |
100
+ | `NOA` / `NACT` | Notice of Allowance |
101
+ | `REM` | Remarks (의견서) |
102
+ | `ABN` | Abandonment |
103
+ | `SRNT` / `SRFW` | Search Report |
104
+ | `EXIN` | Examiner Interview |
105
+ | `RCE` / `RCEX` | Request for Continued Examination |
106
+ | `CTAV` | Advisory Action |
107
+ | `892` / `1449` / `IDS` | Prior Art / IDS |
108
+ | `A*` | Amendment 계열 전체 |
109
+
110
+ ## 생성 파일 구조
111
+
112
+ `file/{app_num}/{app_num}_prosecution.md`:
113
+
114
+ | 섹션 | 내용 |
115
+ |------|------|
116
+ | 타임라인 | 전체 문서 날짜순 표 (XML/PDF 형식 표시) |
117
+ | Office Action 상세 | CTNF/CTFR 거절 항목 전문 |
118
+ | Notice of Allowance 상세 | 허여 청구항 + Examiner's Statement |
119
+ | PDF 전용 문서 | Amendment 등 이미지 PDF 목록 (AI 에이전트 직접 전달용) |
120
+
121
+ ## PyPI 배포
122
+
123
+ ```bash
124
+ uv build
125
+ uv run twine upload dist/*
126
+ ```
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "uspto-oa-cli"
7
+ version = "0.1.0"
8
+ description = "USPTO 특허 심사과정 분석 CLI — 문서 다운로드 · XML 파싱 · MD 생성"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.13"
12
+ keywords = ["uspto", "patent", "office-action", "prosecution", "cli"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Legal Industry",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
22
+ ]
23
+ dependencies = [
24
+ "click>=8.0",
25
+ "pypdf>=6.11.0",
26
+ "requests>=2.34.2",
27
+ "rich>=15.0.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/noaa/uspto-oa-cli"
32
+
33
+ [project.scripts]
34
+ uspto-oa = "oa_cli.cli:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/oa_cli"]
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "python-dotenv>=1.0",
42
+ "hatchling>=1.29.0",
43
+ "twine>=6.0",
44
+ "pytest>=8.0",
45
+ "ruff>=0.4",
46
+ ]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
50
+ addopts = "-v"
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "I"]
@@ -0,0 +1,3 @@
1
+ """odp-oa-cli: USPTO 특허 심사과정 분석 CLI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from oa_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,180 @@
1
+ """USPTO 특허 심사과정 분석 CLI."""
2
+
3
+ import logging
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from oa_cli import __version__
13
+ from oa_cli import config as cfg
14
+
15
+ console = Console()
16
+
17
+
18
+ @click.group(invoke_without_command=True)
19
+ @click.version_option(__version__, prog_name="uspto-oa")
20
+ @click.option("--verbose", "-v", is_flag=True, default=False, help="상세 로그 출력.")
21
+ @click.pass_context
22
+ def main(ctx: click.Context, verbose: bool) -> None:
23
+ """USPTO 특허 심사과정 분석 CLI.
24
+
25
+ \b
26
+ 빠른 시작:
27
+ uspto-oa configure # API 키 저장
28
+ uspto-oa download 16330077 # 문서 다운로드
29
+ uspto-oa extract 16330077 # XML 파싱 → MD 생성
30
+ """
31
+ ctx.ensure_object(dict)
32
+ ctx.obj["verbose"] = verbose
33
+ level = logging.DEBUG if verbose else logging.WARNING
34
+ logging.basicConfig(stream=sys.stderr, level=level, format="%(levelname)s %(message)s")
35
+ if ctx.invoked_subcommand is None:
36
+ click.echo(f"uspto-oa v{__version__}")
37
+ click.echo(ctx.get_help())
38
+
39
+
40
+ # ── configure ────────────────────────────────────────────────────────────────
41
+
42
+ @main.command()
43
+ @click.option("--show", is_flag=True, default=False, help="현재 설정만 표시하고 종료.")
44
+ def configure(show: bool) -> None:
45
+ """API 키와 기본 설정을 저장합니다.
46
+
47
+ \b
48
+ 설정 파일: ~/.oa-cli.toml
49
+ Enter 입력 시 기존 값 유지.
50
+ """
51
+ existing = cfg.load()
52
+ auth_cfg = existing.get("auth", {})
53
+
54
+ click.echo("OA CLI — 설정")
55
+ click.echo("─" * 40)
56
+ click.echo(f"설정 파일: {cfg.CONFIG_PATH}")
57
+ click.echo()
58
+
59
+ current_key = auth_cfg.get("api_key", "")
60
+
61
+ if show:
62
+ click.echo(f" auth.api_key = {cfg.mask_key(current_key)}")
63
+ return
64
+
65
+ new_key = click.prompt(
66
+ f"USPTO API 키 [{cfg.mask_key(current_key)}]",
67
+ default="",
68
+ show_default=False,
69
+ ).strip()
70
+
71
+ final_key = new_key if new_key else current_key
72
+ if not final_key:
73
+ click.echo("\n경고: API 키가 없습니다. 나중에 다시 실행하세요.", err=True)
74
+ return
75
+
76
+ cfg.save({"auth": {"api_key": final_key}})
77
+ click.echo(f"\n설정 저장: {cfg.CONFIG_PATH}")
78
+ click.echo(f" auth.api_key = {cfg.mask_key(final_key)}")
79
+
80
+
81
+ # ── download ─────────────────────────────────────────────────────────────────
82
+
83
+ @main.command()
84
+ @click.argument("application")
85
+ @click.option("--doc-codes", default=None, metavar="CODES",
86
+ help="쉼표 구분 문서 코드 (예: CTNF,CTFR,NOA). 생략 시 전체 수집 대상.")
87
+ @click.option("--output-dir", default=None, metavar="DIR", help="저장 경로 (기본: file/{app_num}/).")
88
+ @click.option("--force", is_flag=True, default=False, help="기존 파일도 재다운로드.")
89
+ @click.option("--api-key", default=None, help="API 키 (설정 파일·환경변수보다 우선).")
90
+ @click.pass_context
91
+ def download(
92
+ ctx: click.Context,
93
+ application: str,
94
+ doc_codes: Optional[str],
95
+ output_dir: Optional[str],
96
+ force: bool,
97
+ api_key: Optional[str],
98
+ ) -> None:
99
+ """심사과정 문서를 다운로드합니다 (XML 우선, PDF 폴백).
100
+
101
+ \b
102
+ 예시:
103
+ oa download 16330077
104
+ oa download US12228644B2
105
+ oa download 16330077 --doc-codes CTNF,CTFR,NOA
106
+ oa download 16330077 --force
107
+ """
108
+ from oa_cli.prosecution.collect import download_docs, normalize_app_number, resolve_patent_number
109
+
110
+ key = cfg.require_api_key(api_key)
111
+ raw = application
112
+
113
+ if raw.upper().startswith("US") and any(c.isalpha() for c in raw[2:]):
114
+ console.print(f"[dim]특허번호 '{raw}' → 출원번호 조회 중...[/dim]")
115
+ app_num = resolve_patent_number(raw, key)
116
+ console.print(f"[dim]출원번호: {app_num}[/dim]")
117
+ else:
118
+ app_num = normalize_app_number(raw)
119
+
120
+ out_dir = output_dir or str(Path.cwd() / "file" / app_num)
121
+ codes = doc_codes.split(",") if doc_codes else None
122
+
123
+ console.print(f"[bold]출원번호[/] {app_num} → [bold]{out_dir}[/]")
124
+ console.print()
125
+
126
+ results = download_docs(
127
+ app_number=app_num,
128
+ api_key=key,
129
+ doc_codes=codes,
130
+ output_dir=out_dir,
131
+ force=force,
132
+ )
133
+
134
+ if not results:
135
+ console.print("[yellow]다운로드된 문서가 없습니다.[/yellow]")
136
+ return
137
+
138
+ table = Table(title=f"다운로드 결과 — {app_num}", show_lines=False)
139
+ table.add_column("날짜", style="cyan", no_wrap=True)
140
+ table.add_column("코드", style="magenta")
141
+ table.add_column("형식", justify="center")
142
+ table.add_column("파일명")
143
+ table.add_column("상태", justify="center")
144
+
145
+ downloaded = skipped = 0
146
+ for r in results:
147
+ status = "[dim]스킵[/dim]" if r["skipped"] else "[green]완료[/green]"
148
+ skipped += r["skipped"]
149
+ downloaded += not r["skipped"]
150
+ fmt = r.get("fmt", "pdf").upper()
151
+ fmt_display = f"[green]{fmt}[/green]" if fmt == "XML" else fmt
152
+ table.add_row(r["date"], r["code"], fmt_display, Path(r["path"]).name, status)
153
+
154
+ console.print(table)
155
+ console.print(f"\n[bold green]{downloaded}개 다운로드[/] / [dim]{skipped}개 스킵[/]")
156
+
157
+
158
+ # ── extract ──────────────────────────────────────────────────────────────────
159
+
160
+ @main.command()
161
+ @click.argument("application")
162
+ @click.option("--output-dir", default=None, metavar="DIR", help="파일 디렉토리 (기본: file/{app_num}/).")
163
+ def extract(application: str, output_dir: Optional[str]) -> None:
164
+ """다운로드된 XML 파일을 파싱해 prosecution.md 를 생성합니다.
165
+
166
+ \b
167
+ 예시:
168
+ oa extract 16330077
169
+ # 결과: file/16330077/16330077_prosecution.md
170
+ """
171
+ from oa_cli.prosecution.extract import extract as do_extract
172
+ from oa_cli.prosecution.collect import normalize_app_number
173
+
174
+ app_num = normalize_app_number(application)
175
+ file_dir = output_dir or str(Path.cwd() / "file" / app_num)
176
+ out_path = str(Path(file_dir) / f"{app_num}_prosecution.md")
177
+
178
+ console.print(f"[bold]출원번호[/] {app_num} → [bold]{out_path}[/]")
179
+ do_extract(app_num, file_dir=file_dir, output_path=out_path)
180
+ console.print(f"[bold green]완료[/] {out_path}")