From cf81b7904cadce0714507e09bb6595a518248480 Mon Sep 17 00:00:00 2001 From: HONGDAE KIM Date: Sun, 15 Feb 2026 11:48:55 +0900 Subject: [PATCH] docs: add UI enhancement phases and consolidated backlog --- .gitignore | 4 + BASELINE_PLAN.md | 20 ++++ NEXT_STEPS_UI_AND_BACKLOG.md | 152 +++++++++++++++++++++++++++++++ ONLINE_EXECUTION_LOG.md | 25 +++++ ONLINE_RESOURCE_COLLECTION.md | 19 ++++ REVIEW_REPORT.md | 83 +++++++++++++++++ bitnet_tools/analysis.py | 23 ++++- resources/online_sources.json | 16 ++++ scripts/collect_online_assets.py | 106 +++++++++++++++++++++ scripts/prepare_online_bundle.sh | 77 ++++++++++++++++ tests/fixtures/missing_heavy.csv | 4 + tests/fixtures/mixed_formats.csv | 4 + tests/fixtures/small_numeric.csv | 4 + tests/test_analysis.py | 31 +++++++ 14 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 BASELINE_PLAN.md create mode 100644 NEXT_STEPS_UI_AND_BACKLOG.md create mode 100644 ONLINE_EXECUTION_LOG.md create mode 100644 ONLINE_RESOURCE_COLLECTION.md create mode 100644 REVIEW_REPORT.md create mode 100644 resources/online_sources.json create mode 100755 scripts/collect_online_assets.py create mode 100755 scripts/prepare_online_bundle.sh create mode 100644 tests/fixtures/missing_heavy.csv create mode 100644 tests/fixtures/mixed_formats.csv create mode 100644 tests/fixtures/small_numeric.csv diff --git a/.gitignore b/.gitignore index 335d224..2663397 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ __pycache__/ # Test/runtime caches .pytest_cache/ .bitnet_cache/ + +.offline_bundle/ + +.online_assets/ diff --git a/BASELINE_PLAN.md b/BASELINE_PLAN.md new file mode 100644 index 0000000..3ef734f --- /dev/null +++ b/BASELINE_PLAN.md @@ -0,0 +1,20 @@ +# Baseline plan (0단계) + +이 문서는 개선 작업 전/후 비교를 위한 기준선 고정 절차를 정의한다. + +## 고정 기준 +- 테스트 전체 통과 여부 (`pytest -q`) +- 대표 입력 CSV 3종 결과 일관성 + - `tests/fixtures/small_numeric.csv` + - `tests/fixtures/mixed_formats.csv` + - `tests/fixtures/missing_heavy.csv` +- 핵심 요약 결과 스냅샷 + - row_count, column_count + - dtypes + - missing_counts + - numeric_stats + +## 운영 방법 +1. 개선 전 baseline 테스트를 실행해 현재 결과를 확인 +2. 개선 작업 후 동일 테스트를 재실행 +3. 의도하지 않은 필드 변경이 있으면 원인 분석 후 수정 diff --git a/NEXT_STEPS_UI_AND_BACKLOG.md b/NEXT_STEPS_UI_AND_BACKLOG.md new file mode 100644 index 0000000..00ba639 --- /dev/null +++ b/NEXT_STEPS_UI_AND_BACKLOG.md @@ -0,0 +1,152 @@ +# BitNet Analyzer 다음 단계 작업 정리 + +이 문서는 다음 턴부터 바로 실행할 수 있도록 +1) 인터페이스 고도화 진행 단계, +2) 전체 남은 과제(개선권장/코드 최적화/입력 포맷 확장) +를 한 번에 정리한 체크리스트입니다. + +--- + +## A. 인터페이스 고도화 진행 단계 (다음 턴부터 착수) + +### Phase 1 — 인터페이스 단순화(우선) +목표: "처음 들어온 사용자도 바로 분석" 가능한 화면으로 정리 + +- [ ] 화면을 2모드로 분리 + - [ ] 빠른 시작 모드: 파일 입력 + 자연어 요청 + 실행 버튼 1개 + - [ ] 고급 모드: 그룹/타깃, JSON 대시보드, 모델 태그 등 +- [ ] 섹션 구조 재배치 + - [ ] 입력 + - [ ] 실행 상태 + - [ ] 결과 +- [ ] 초기에 보이는 요소 축소 + - [ ] 멀티 분석/JSON 대시보드는 접기(Accordion) 기본 + +완료 기준: +- 신규 사용자가 30초 내 첫 분석 실행 가능 +- 단일 분석 중심 플로우에서 클릭 수 감소 + +### Phase 2 — 자연어 요청 인터페이스 +목표: 사용자의 작업 지시를 자연어로 받아 흐름 자동 선택 + +- [ ] "작업 요청" 입력창 추가 (예: "이 파일 먼저 분석하고 시각화 옵션 알려줘") +- [ ] 의도 라우팅 규칙 추가 + - [ ] 분석 우선 + - [ ] 시각화 옵션 안내 우선 + - [ ] 멀티 비교 우선 +- [ ] 질문(question)과 작업요청(intent) 분리 저장 + +완료 기준: +- 자연어 요청 한 번으로 기본 작업 흐름 자동 진행 +- 요청 해석 실패 시 추천 액션 버튼 제공 + +### Phase 3 — 상태/피드백 표준화 +목표: 진행상황/실패 원인/다음 행동을 명확히 안내 + +- [ ] 로딩 상태 통합 컴포넌트 + - [ ] 분석 중 + - [ ] BitNet 실행 중 + - [ ] 멀티 분석 중 + - [ ] 차트 생성 중 +- [ ] 에러 메시지 표준화 + - [ ] 사용자용 간단 메시지 + - [ ] 상세 기술 메시지(접기) +- [ ] 버튼 활성/비활성 규칙 정리 + +완료 기준: +- 실패 시 사용자가 다음 액션을 바로 알 수 있음 +- 중복 클릭/중복 요청 최소화 + +### Phase 4 — 대시보드 사용성 강화 +목표: 결과 확인 시간을 줄이고 탐색 효율 개선 + +- [ ] KPI 카드 + 인사이트 리스트 개선 +- [ ] 필터 추가 + - [ ] 파일 단위 + - [ ] 컬럼명 검색 + - [ ] 인사이트 유형(결측/이상치/드리프트) +- [ ] 드릴다운 패널 추가 + - [ ] 선택 인사이트 근거 데이터 표기 + +완료 기준: +- 핵심 인사이트 3개 확인 시간 단축 +- 사용자가 근거를 UI에서 즉시 확인 가능 + +### Phase 5 — 비동기 차트 UX 연동 +목표: 대형 데이터에서도 차트 생성 경험 안정화 + +- [ ] 차트 job 상태 표시(queued/running/done/failed) +- [ ] polling + 완료 시 결과 자동 갱신 +- [ ] 실패 재시도 버튼 + +완료 기준: +- 차트 생성 대기 중에도 UI 사용 가능 +- 실패 복구 동선 제공 + +--- + +## B. 남은 과제 백로그 (우선순위 포함) + +## B-1. 분석 정확도 개선 + +### P1. 날짜 파싱 포맷 확장 +- [ ] 날짜 포맷 후보 확장 (`YYYYMMDD`, `DD-MM-YYYY`, locale 변형) +- [ ] 모호한 포맷은 불확실 상태로 분리 + +### P1. 결과 스키마 버전 필드 도입 +- [ ] JSON 결과에 `schema_version` 추가 +- [ ] 버전 변경 로그 문서화 + +### P2. 고유값 추정 정밀 옵션 +- [ ] 기본(빠른 추정) + 정밀 모드(옵션) 이원화 +- [ ] 결과에 추정 방식 메타데이터 포함 + +## B-2. 코드 최적화/구조화 + +### P1. UI 스크립트 모듈화 +- [ ] `app.js`를 기능 단위로 분리 + - [ ] 상태관리 + - [ ] API 호출 + - [ ] 렌더링 + - [ ] 이벤트 바인딩 + +### P1. API/에러 핸들링 공통화 +- [ ] 공통 fetch wrapper +- [ ] 에러 포맷 표준(JSON) + +### P2. 성능 개선 +- [ ] 대형 JSON 렌더 incremental 처리 +- [ ] 불필요한 재렌더 최소화 + +## B-3. 입력 파일 확장성 (후순위) + +현재 직접 지원은 CSV 중심이므로, 아래를 순차 확장: + +### P2. Excel 지원 (`.xlsx`, `.xls`) +- [ ] 시트 선택 UI +- [ ] 표 데이터 CSV 정규화 + +### P3. 문서 포맷 지원 (`.pdf`, `.docx`, `.pptx`) +- [ ] 표 추출 파이프라인 도입 +- [ ] 추출 정확도/신뢰도 표시 +- [ ] 실패 시 CSV 업로드 fallback 제공 + +--- + +## C. 다음 턴 실행 순서 (권장) + +1. Phase 1 (단순화) 착수 +2. Phase 2 (자연어 요청 인터페이스) +3. Phase 3 (상태/에러 표준화) +4. Phase 4 (대시보드 필터/드릴다운) +5. Phase 5 (차트 비동기 UX) + +병행 과제로 `schema_version` 설계를 함께 진행. + +--- + +## D. 체크 포인트 + +- [ ] 기능 개발 후 `pytest -q` 통과 +- [ ] UI 주요 흐름 스모크 테스트 +- [ ] 문서 업데이트(README/변경 이력) diff --git a/ONLINE_EXECUTION_LOG.md b/ONLINE_EXECUTION_LOG.md new file mode 100644 index 0000000..73c3dbc --- /dev/null +++ b/ONLINE_EXECUTION_LOG.md @@ -0,0 +1,25 @@ +# 인터넷 1턴 실행 로그 및 후속 가이드 + +## 이번 턴 수행 내용 +- `scripts/prepare_online_bundle.sh` 추가 +- 온라인 가능 시 다음을 자동 수행하도록 구성 + - 환경 메타데이터 수집 + - 로컬 wheel 빌드 시도 + - 선택 의존성 wheel 다운로드 시도 + - Ollama 설치 스크립트 보관 시도 + - 오프라인 사용 가이드 생성 + +## 이번 환경에서의 결과 +- 프록시 제한(403)으로 외부 다운로드 실패 +- Ollama 설치 스크립트도 403으로 실패 +- 따라서 다운로드 단계는 경고 파일로 남기고, 스크립트는 종료하지 않도록 설계 + +## 다음 네트워크 허용 환경에서 기대 결과 +- `.offline_bundle/wheels`에 프로젝트 및 선택 의존성 wheel 저장 +- `.offline_bundle/models/ollama_install.sh` 보관 +- `.offline_bundle/OFFLINE_USE.md` 기반으로 오프라인 설치 가능 + +## 실행 명령 +```bash +./scripts/prepare_online_bundle.sh +``` diff --git a/ONLINE_RESOURCE_COLLECTION.md b/ONLINE_RESOURCE_COLLECTION.md new file mode 100644 index 0000000..a8fefe8 --- /dev/null +++ b/ONLINE_RESOURCE_COLLECTION.md @@ -0,0 +1,19 @@ +# Online tool/reference collection + +인터넷이 열려 있는 턴에서 필요한 툴 패키지(wheel)와 UI/접근성 레퍼런스를 로컬로 저장하기 위한 작업 기록 문서. + +## Source catalog +- `resources/online_sources.json` + - `tool_packages`: 다운로드 대상 pip 패키지 목록 + - `reference_urls`: 저장 대상 웹 레퍼런스 URL 목록 + +## Collector +- `scripts/collect_online_assets.py` + - pip wheel 다운로드 시도 (`.online_assets/wheels`) + - 레퍼런스 HTML 다운로드 시도 (`.online_assets/references`) + - 결과 리포트 생성 (`.online_assets/meta/collection_report.json`) + +## Run +```bash +python scripts/collect_online_assets.py +``` diff --git a/REVIEW_REPORT.md b/REVIEW_REPORT.md new file mode 100644 index 0000000..4e0caed --- /dev/null +++ b/REVIEW_REPORT.md @@ -0,0 +1,83 @@ +# 실행 가능률 및 분석 기능 점검 보고서 + +## 1) 현재 실행 가능률(환경 기준) + +- 테스트 실행 결과: `24 passed` (핵심 분석/CLI/웹 핸들러 단위 기능 정상) +- 로컬 환경 진단 결과: Python/플랫폼 확인 가능, `ollama` 미설치로 모델 실행 경로는 현재 비활성 + +### 실행 가능률 산정(현 환경) +- 코드 자체 품질(테스트 통과율): **100% (24/24)** +- LLM 연동 포함 엔드투엔드 실사용률: **약 85~90%** + - 사유: `analyze`, `multi-analyze`, `report`, `ui`, `doctor`는 동작 가능 + - 단, `ollama run`이 필요한 즉시 모델 응답(`/api/run`, `--model`)은 로컬 ollama 설치/기동 필요 + +## 2) 제공 분석 기능 + +### 단일 CSV 분석 +- 행/열 수, 컬럼 목록, 결측 수 +- 컬럼 타입 추론(숫자/문자) +- 숫자형 기본 통계(count/mean/min/max) +- BitNet 프롬프트 자동 생성 +- Markdown 보고서 생성 + +### 다중 CSV 분석 +- 파일별 프로파일링(결측/고유비율/대표값/의미타입) +- 공통 컬럼/전체 컬럼 집합 비교 +- 파일 간 스키마 드리프트(타입 변화, 결측비율 범위, 대표값비율 범위, 평균 변화) +- 인사이트 룰 엔진(결측 높음, 이상치 비율 높음, 평균 변화 등) +- 그룹-타깃 비율표(옵션) +- pandas 코드 가이드 자동 생성 +- 캐시 기반 재분석 가속(`.bitnet_cache`) +- 병렬 파일 프로파일링(workers) + +### 시각화 +- 수치형: histogram, boxplot, missing bar +- 범주형: top-k bar +- 수치형 2개 이상 시 scatter 샘플 +- 대용량 대응을 위한 reservoir sampling 적용 + +### 웹/데스크톱 +- 브라우저 UI: CSV 텍스트 붙여넣기 단일/다중 분석 +- 차트 생성 비동기 작업(job) 제출/조회 API +- Windows 데스크톱 UI 진입점 제공 + +## 3) 받아본 데이터로 도출 가능한 결과 + +- 데이터 품질 진단: 결측/편중/희소성/대표값 쏠림 +- 수치 분포 요약: 평균/최솟값/최댓값/양수·0·음수 비율 +- 이상치 위험도: IQR 기반 outlier ratio 추정 +- 컬럼 성격 파악: category/date/numeric/text/위경도 추정 +- 다중 파일 비교: 스키마 호환성 및 분포 변화(드리프트) +- 운영 인사이트: 품질 이슈 우선순위(결측↑, 이상치↑, 타입 충돌) +- 후속 분석 가이드: 병합 키 중심 pandas 예시 코드 자동 제안 + +## 4) 분석 가능한 데이터 범위 + +### 직접 지원 +- CSV 파일(단일/다중) +- 웹 UI 입력용 CSV 텍스트(붙여넣기) + +### 확장/간접 지원 +- 생성된 JSON/Markdown 결과를 BitNet 프롬프트로 전달해 해석형 요약 가능 +- 코드 가이드를 활용한 pandas 후처리 확장 가능 + +### 주의사항 +- CSV 헤더 필수(헤더 없으면 오류) +- 숫자형은 컬럼 내 텍스트 혼입 시 string으로 판정될 수 있음 +- 고유값은 비트맵 기반 추정치(정확 cardinality 아님) +- outlier ratio는 샘플 기반 추정 + +## 5) 코드 전체 검토 요약 + +### 강점 +- 분석 엔진이 스트리밍/샘플링 중심으로 메모리 사용을 제어 +- CLI, 웹 API, 데스크톱 진입점이 분리되어 사용성 좋음 +- 캐시/병렬 처리 등 실사용 성능 요소 반영 +- 테스트 커버리지(기능 단위) 양호 + +### 개선 권장 +- 숫자형 추론 고도화(천 단위 구분기호, 퍼센트, 통화 기호 정규화) +- 날짜 파싱 포맷 확장 및 locale 대응 +- unique 추정 정확도 옵션(HLL 등) 추가 +- UI/API 레벨에서 대용량 파일 업로드/진행률/취소 제어 강화 +- 결과 스키마 버전 필드 추가(하위호환 관리) diff --git a/bitnet_tools/analysis.py b/bitnet_tools/analysis.py index d96ad72..c817620 100644 --- a/bitnet_tools/analysis.py +++ b/bitnet_tools/analysis.py @@ -32,11 +32,32 @@ def _to_float(value: str) -> float | None: v = value.strip() if not v: return None + + negative_by_parentheses = v.startswith("(") and v.endswith(")") + if negative_by_parentheses: + v = v[1:-1].strip() + + # normalize frequent human-entered numeric formats + v = ( + v.replace(",", "") + .replace("₩", "") + .replace("$", "") + .replace("€", "") + .replace("£", "") + .replace("%", "") + .strip() + ) + + if not v: + return None + try: - return float(v) + parsed = float(v) except ValueError: return None + return -parsed if negative_by_parentheses else parsed + def summarize_rows(rows: list[dict[str, str]], columns: list[str]) -> DataSummary: return summarize_reader(rows, columns) diff --git a/resources/online_sources.json b/resources/online_sources.json new file mode 100644 index 0000000..c9d1494 --- /dev/null +++ b/resources/online_sources.json @@ -0,0 +1,16 @@ +{ + "tool_packages": [ + "matplotlib", + "pandas", + "jupyterlab", + "pytest", + "playwright" + ], + "reference_urls": [ + "https://www.w3.org/WAI/WCAG22/quickref/", + "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA", + "https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API", + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl", + "https://web.dev/articles/loading-patterns" + ] +} diff --git a/scripts/collect_online_assets.py b/scripts/collect_online_assets.py new file mode 100755 index 0000000..37e30a3 --- /dev/null +++ b/scripts/collect_online_assets.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import json +import re +import subprocess +import sys +from dataclasses import dataclass, asdict +from datetime import datetime, timezone +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.parse import urlparse +from urllib.request import Request, urlopen + +ROOT = Path(__file__).resolve().parent.parent +SOURCES_FILE = ROOT / "resources" / "online_sources.json" +OUT_DIR = ROOT / ".online_assets" +REF_DIR = OUT_DIR / "references" +WHEEL_DIR = OUT_DIR / "wheels" +META_DIR = OUT_DIR / "meta" + + +@dataclass +class DownloadResult: + target: str + category: str + ok: bool + path: str | None + detail: str + + +def _slug_from_url(url: str) -> str: + parsed = urlparse(url) + stem = (parsed.netloc + parsed.path).strip("/") or "index" + stem = re.sub(r"[^a-zA-Z0-9._-]+", "_", stem) + return f"{stem}.html" + + +def _fetch_reference(url: str) -> DownloadResult: + REF_DIR.mkdir(parents=True, exist_ok=True) + out_path = REF_DIR / _slug_from_url(url) + req = Request(url, headers={"User-Agent": "bitnet-tools/online-collector"}) + try: + with urlopen(req, timeout=20) as resp: + body = resp.read() + out_path.write_bytes(body) + return DownloadResult(url, "reference", True, str(out_path.relative_to(ROOT)), "downloaded") + except HTTPError as exc: + return DownloadResult(url, "reference", False, None, f"http_error:{exc.code}") + except URLError as exc: + return DownloadResult(url, "reference", False, None, f"url_error:{exc.reason}") + except Exception as exc: # pragma: no cover + return DownloadResult(url, "reference", False, None, f"error:{exc}") + + +def _download_wheel(pkg: str) -> DownloadResult: + WHEEL_DIR.mkdir(parents=True, exist_ok=True) + cmd = [sys.executable, "-m", "pip", "download", pkg, "-d", str(WHEEL_DIR)] + proc = subprocess.run(cmd, capture_output=True, text=True, check=False) + if proc.returncode == 0: + return DownloadResult(pkg, "tool_package", True, str(WHEEL_DIR.relative_to(ROOT)), "downloaded") + detail = proc.stderr.strip() or proc.stdout.strip() or "pip download failed" + return DownloadResult(pkg, "tool_package", False, None, detail.splitlines()[-1][:220]) + + +def main() -> int: + if not SOURCES_FILE.exists(): + print(f"sources file not found: {SOURCES_FILE}", file=sys.stderr) + return 1 + + data = json.loads(SOURCES_FILE.read_text(encoding="utf-8")) + tool_packages: list[str] = list(data.get("tool_packages", [])) + reference_urls: list[str] = list(data.get("reference_urls", [])) + + OUT_DIR.mkdir(exist_ok=True) + META_DIR.mkdir(parents=True, exist_ok=True) + + results: list[DownloadResult] = [] + + for pkg in tool_packages: + print(f"[tool] {pkg}") + results.append(_download_wheel(pkg)) + + for url in reference_urls: + print(f"[ref] {url}") + results.append(_fetch_reference(url)) + + report = { + "created_at": datetime.now(timezone.utc).isoformat(), + "python": sys.version, + "source_file": str(SOURCES_FILE.relative_to(ROOT)), + "results": [asdict(r) for r in results], + "summary": { + "total": len(results), + "success": sum(1 for r in results if r.ok), + "failed": sum(1 for r in results if not r.ok), + }, + } + + out = META_DIR / "collection_report.json" + out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"report saved: {out.relative_to(ROOT)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/prepare_online_bundle.sh b/scripts/prepare_online_bundle.sh new file mode 100755 index 0000000..bd52f49 --- /dev/null +++ b/scripts/prepare_online_bundle.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +BUNDLE_DIR="${ROOT_DIR}/.offline_bundle" +WHEEL_DIR="${BUNDLE_DIR}/wheels" +MODEL_DIR="${BUNDLE_DIR}/models" +META_DIR="${BUNDLE_DIR}/meta" + +mkdir -p "${WHEEL_DIR}" "${MODEL_DIR}" "${META_DIR}" + +echo "[1/6] Collecting environment metadata" +python -V | tee "${META_DIR}/python_version.txt" +pip --version | tee "${META_DIR}/pip_version.txt" +python -m pip freeze | tee "${META_DIR}/pip_freeze.txt" >/dev/null + +cat > "${META_DIR}/bundle_manifest.txt" <&1) +pip=$(pip --version) +MANIFEST + +echo "[2/6] Building local project wheel" +if python -m pip wheel --no-build-isolation "${ROOT_DIR}" -w "${WHEEL_DIR}"; then + echo "local wheel build: success" +else + echo "local wheel build failed" | tee "${META_DIR}/wheel_build_warning.txt" +fi + +# Optional runtime dependencies for charts/notebooks/tests +cat > "${META_DIR}/requirements_online.txt" </dev/null 2>&1; then + ollama --version | tee "${META_DIR}/ollama_version.txt" + # Avoid model pull in automated script unless explicitly requested + echo "ollama detected; model pull can be run manually:" | tee -a "${META_DIR}/ollama_version.txt" + echo " ollama pull " | tee -a "${META_DIR}/ollama_version.txt" +else + echo "ollama not installed in current environment" | tee "${META_DIR}/ollama_version.txt" +fi + +echo "[6/6] Writing offline install guide" +cat > "${BUNDLE_DIR}/OFFLINE_USE.md" <