DEV Community

wide CSV 여러 개를 EAV로 모아 gold mart 만들기

Scenario

현실의 데이터 소스는 한 가지 모양으로 오지 않는다. 같은 의미의 값도 어떤 파일에서는 생산수량, 다른 파일에서는 units, 또 다른 파일에서는 made로 올 수 있다. 온도도 어떤 곳은 섭씨, 어떤 곳은 화씨일 수 있다. 이걸 매번 pipeline code에 if source == ...로 박기 시작하면 source가 늘 때마다 코드가 지저분해진다.

manufacturing-data-platform-mini의 EAV mini slice는 이 문제를 작게 다룬다. 여러 wide CSV를 mapping config로 표준 attribute에 맞춘 뒤, EAV long format으로 모으고, 다시 gold metric mart로 pivot/aggregate한다. 데이터는 모두 synthetic이고, 회사 코드·고객 데이터·실제 schema는 쓰지 않았다.

서로 다른 공장/라인/벤더에서 비슷한 제조 지표 파일이 들어온다. 예:

  • plant_a.csv: 설비ID, 생산수량, 불량수, 온도C, 압력kPa
  • plant_b.csv: machine_id, output_units, defects, temp_f, pressure_bar
  • vendor_d.csv: unit_name, made, scrap, deg_c, kpa

비즈니스적으로는 같은 지표를 보고 싶다: units_produced, defect_count, temperature_c, pressure_kpa. 문제는 source마다 column name과 unit이 다르다는 점이다.

Decision Pressure

단순 구현은 source마다 코드를 늘린다.

  • if source == "plant_a": 생산수량을 units_produced로 읽는다
  • if source == "plant_b": output_units를 units_produced로 읽는다, temp_f를 섭씨로 변환한다
  • if source == "vendor_d": made를 units_produced로 읽는다

이 방식은 작게는 빨라 보이지만 source가 늘수록 문제가 된다. 새 파일 형식마다 pipeline code를 고쳐야 한다. column mapping과 transform logic이 섞인다. unit conversion이 흩어진다. quality check가 source별로 갈라진다. gold mart grain을 설명하기 어려워진다. 그래서 mapping은 config로 빼고, pipeline은 표준 attribute를 처리하게 만들었다.

Options

Option Result Risk
source별 hard-coded parser 처음엔 빠름 source가 늘 때 code change 반복
모든 source를 wide table 하나로 합치기 보기 쉬움 sparse/heterogeneous column 폭발
EAV long format 이종 attribute를 표준 형태로 모음 pivot/quality 설계가 필요
full mapping DSL/rules engine 유연함 mini project에는 과함

이 프로젝트의 선택은 단순한 JSON mapping + EAV long + gold pivot이다.

Decision

각 source는 JSON config로 자신의 column을 표준 attribute에 매핑한다.

  • output_units -> units_produced
  • temp_f -> temperature_c with f_to_c
  • pressure_bar -> pressure_kpa with bar_to_kpa

Pipeline 흐름:

wide CSVs -> mapping configs -> EAV long rows -> gold entity_daily_metrics -> quality checks -> catalog/lineage

EAV row의 핵심 shape:

entity_id, business_date, attribute, value, value_type, source_id, source_file_id

source_file_id는 file content hash다. 즉 EAV slice도 file-level idempotency를 갖는다. 더 정확히는 source_file_id가 각 source file의 hash이고, run-level idempotency key는 모든 source file hash를 합친 source_hash다. 그래서 같은 source 묶음을 같은 business_date로 다시 처리하면 기존 successful run을 재사용한다.

Evidence

관련 코드와 config:

  • src/manufacturing_data_platform/pipeline/eav.py
  • src/manufacturing_data_platform/pipeline/run_eav.py
  • src/manufacturing_data_platform/pipeline/sample_eav.py
  • config/eav_mappings/plant_a.json
  • config/eav_mappings/plant_b.json
  • config/eav_mappings/line_c.json
  • tests/test_eav_pipeline.py

핵심 테스트:

  • test_transform_to_eav_maps_and_converts_units
  • test_transform_to_eav_captures_type_errors_gracefully
  • test_transform_eav_to_gold_aggregates_sum_and_avg
  • test_eav_run_passes_and_unifies_three_formats
  • test_new_format_is_onboarded_by_adding_one_config
  • test_eav_idempotent_rerun_is_skipped
  • test_mapping_coverage_fails_when_required_attribute_unmapped
  • test_unmapped_columns_warn_does_not_fail

특히 test_new_format_is_onboarded_by_adding_one_config는 중요하다. 이 테스트는 기존 3개 sample source가 있는 상태에서 vendor_d.csvvendor_d.json을 추가한다. pipeline code는 바꾸지 않는다. 그 다음 run 결과에 새 entity VD-1이 들어오는지 확인한다. 즉 claim은 이렇다. 새 파일 형식 하나는 mapping config 추가만으로 온보딩된다. pipeline code change는 없다.

검증 로그:

2026-06-30 EAV mini slice:
  pytest: 33 passed
  EAV CLI run 1: status=processed, quality_passed=true
  EAV CLI run 2: status=skipped
  conservation: EAV units 540 == gold units 540

2026-07-08 publication readiness check:
  pytest: 33 passed
  EAV JSON CLI: passed, status=processed, quality_passed=true

2026-07-10 B3 publication evidence check:
  pytest: 35 passed
  EAV JSON CLI run 1: status=processed, quality_passed=true
  EAV JSON CLI run 2: status=skipped
  gold_rows=4, gold_units_total=540, gold_defects_total=12

실제 CLI 검증 요약:

{
  "run1_status": "processed",
  "run2_status": "skipped",
  "dataset_id": "manufacturing_wide_eav",
  "business_date": "2026-06-29",
  "gold_rows": 4,
  "entities": ["EQP-A1", "EQP-A2", "LN-C1", "MC-B1"],
  "gold_units_total": 540,
  "gold_defects_total": 12,
  "lineage_layers": ["bronze", "silver_eav", "gold"]
}

Quality Checks

EAV slice도 단순 transform으로 끝나지 않는다. Quality checks:

  • mapping_coverage
  • unmapped_source_columns (warn)
  • not_null_value
  • accepted_values_attribute
  • value_type_valid
  • numeric_range_within_bounds
  • eav_to_gold_conservation
  • freshness_business_date
  • schema_drift

EAV에서 gold로 pivot/aggregate할 때 additive measure가 보존되는지도 확인한다.

  • EAV units_produced total == gold units_produced total
  • EAV defect_count total == gold defect_count total

그리고 manufacturing slice와 달리, EAV slice는 unparseable value를 바로 crash하지 않는다.

unparseable value -> value = None -> value_type_valid = fail

즉 bad value를 quality failure로 드러낸다.

Claim Boundary

정직한 한계:

  • 데이터는 fully synthetic이다. 회사 코드, 고객 데이터, 실제 schema를 쓰지 않았다.
  • full mapping DSL이나 UI는 없다.
  • EAV가 모든 데이터 모델링 문제의 정답이라는 주장이 아니다.
  • Spark/Iceberg/Kafka는 이 slice에 구현되어 있지 않다.
  • 실무 경험 claim도 조심해서 말해야 한다.

말해도 되는 것:

  • 실무에서는 EAV 기반 구조를 운영·개선하며 다양한 파일 양식을 처리했다.
  • 개인 프로젝트에서는 synthetic data로 wide -> EAV -> gold flow를 직접 구현했다.

말하면 안 되는 것:

  • 실무 EAV 시스템을 내가 처음부터 설계/구현했다.
  • 회사 schema를 이 프로젝트에 가져왔다.
  • real customer data로 검증했다.

Why This Matters For The Portfolio

이 slice는 단순히 EAV라는 단어를 넣기 위한 것이 아니다. 보여주는 능력:

  • 이종 source를 표준 attribute로 harmonize
  • mapping config와 transform logic 분리
  • wide -> long -> mart 모델링
  • unit conversion
  • data quality check 설계
  • 새 format 온보딩 contract
  • clean-room boundary 관리

즉 데이터 엔지니어링 포트폴리오에서 "파이프라인을 만들었다"보다 한 단계 더 나아가, source 다양성과 모델링 압력을 다뤘다는 증거가 된다.

정리

서로 다른 wide CSV를 하나의 mart로 합치려면 컬럼 이름만 맞추는 것으로는 부족하다. 어떤 source column이 어떤 표준 attribute가 되는지, 단위 변환은 어디서 하는지, 새 format은 어떤 계약으로 들어오는지까지 정해야 한다.

이 slice에서는 synthetic data만 사용해 wide -> EAV -> gold 흐름을 만들었다. 새 파일 형식은 pipeline code 변경 없이 mapping config 하나로 온보딩되고, mapping coverage, value type, conservation check로 결과를 검증한다. 실무 경험과 개인 프로젝트 구현 claim은 분리해서 말한다.

Comments

No comments yet. Start the discussion.