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/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/tests/fixtures/missing_heavy.csv b/tests/fixtures/missing_heavy.csv new file mode 100644 index 0000000..44d14f8 --- /dev/null +++ b/tests/fixtures/missing_heavy.csv @@ -0,0 +1,4 @@ +a,b,c +1,,x +,2, +,,z diff --git a/tests/fixtures/mixed_formats.csv b/tests/fixtures/mixed_formats.csv new file mode 100644 index 0000000..400b482 --- /dev/null +++ b/tests/fixtures/mixed_formats.csv @@ -0,0 +1,4 @@ +id,price,discount,note +1,"1,200",12.5%,ok +2,₩4500,5%,good +3,"$3,000",,n/a diff --git a/tests/fixtures/small_numeric.csv b/tests/fixtures/small_numeric.csv new file mode 100644 index 0000000..329f246 --- /dev/null +++ b/tests/fixtures/small_numeric.csv @@ -0,0 +1,4 @@ +id,amount,qty +1,10.5,2 +2,20,3 +3,30.25,1 diff --git a/tests/test_analysis.py b/tests/test_analysis.py index e789069..0ece20a 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -1,3 +1,5 @@ +from pathlib import Path + from bitnet_tools.analysis import ( build_analysis_payload, build_analysis_payload_from_csv_text, @@ -152,3 +154,32 @@ def test_multi_csv_with_parallel_workers(tmp_path): assert result["file_count"] == 2 assert [f["path"] for f in result["files"]] == [str(p1), str(p2)] + + +def test_to_float_normalizes_currency_comma_percent_parentheses(): + from bitnet_tools.analysis import _to_float + + assert _to_float("1,234.5") == 1234.5 + assert _to_float("₩4,500") == 4500.0 + assert _to_float("$3,000") == 3000.0 + assert _to_float("12.5%") == 12.5 + assert _to_float("(99.1)") == -99.1 + + +def test_baseline_fixture_summaries_are_stable(tmp_path): + from bitnet_tools.analysis import build_analysis_payload + + root = Path(__file__).parent / "fixtures" + + small = build_analysis_payload(root / "small_numeric.csv", "baseline") + assert small["summary"]["row_count"] == 3 + assert small["summary"]["column_count"] == 3 + assert small["summary"]["dtypes"]["amount"] == "float" + + mixed = build_analysis_payload(root / "mixed_formats.csv", "baseline") + assert mixed["summary"]["dtypes"]["price"] == "float" + assert mixed["summary"]["dtypes"]["discount"] == "float" + assert mixed["summary"]["missing_counts"]["discount"] == 1 + + missing = build_analysis_payload(root / "missing_heavy.csv", "baseline") + assert missing["summary"]["missing_counts"] == {"a": 2, "b": 2, "c": 1}