This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle.
제가 만든 것 (What I Built)
공급업체가 스프레드시트를 보냅니다. 가격 열에는 12.100,00이라고 적혀 있습니다. 헤더에는 c/IVA (VAT 포함)라고 되어 있습니다. 또 다른 열에는 10+5가 있고, 또 다른 열에는 100이 있습니다. 단위당 비용은 얼마일까요?
비용은 85.50입니다: 두 번의 연쇄 할인(15% 아님), VAT 제거, 박스를 100개 단위로 분할했습니다. 작은 아르헨티나 상점들은 공급업체 목록이 도착할 때마다, 모든 공급업체에 대해, 각 공급업체 고유의 형식으로 이것을 수동으로 처리합니다. 그리고 다음 주에는 열 중 하나가 이동해 있을 것입니다.
Pricor (price + order)는 이러한 파일을 일관되고 가져올 수 있는 제품 기록(product records)으로 변환하는 로컬 앱입니다: 공급업체 코드, 설명, 순 단위 비용 및 통화, 그리고 행 수준 추적성(row-level traceability)을 제공합니다. Excel 또는 CSV를 읽고, 각 열이 무엇을 의미하는지 제안하며, 파일에서 명시되지 않은 모든 것에 대해 질문하고, 사용자가 승인하기 전까지 원본 셀부터 최종 비용까지의 모든 숫자를 보여줍니다. 거부된 행은 계속 표시되고 내보낼 수 있으며; 아무것도 조용히 누락되지 않습니다.
Pricor는 파일을 준비할 뿐이며, 상점의 관리 시스템을 대체하지 않습니다. 그러한 시스템들은 이미 가격 업데이트를 가져오지만, 오직 그들 고유의 고정된 형식으로만 가능합니다.
누가 비용을 지불할까요? 제가 생각하는 부업은 이 도구를 기반으로 구축된 상점의 반복적인 공급업체 가져오기(recurring supplier imports) 설정 및 지원입니다. 이것은 가설일 뿐이며, 검증된 수요는 아닙니다: 아직 돈을 내는 고객을 확보하거나 절약되는 시간을 측정하지 못했습니다.
데모 (Demo)
Pricor demo: a supplier row becomes 85.50 per unit, through columns read by local models, questions the user answers, and an exact receipt
위 클립(21초, GIF처럼 무음)은 Laya와 Gemma 4 E4B를 로컬에서 실행하는 동안 녹화된 화면들로 제작되었으며, 기록을 위해 작성된 작은 가상의 목록(
실제 공개 공급업체 가격 페이지의 두 목록에 대한 더 긴 브라우저 리허설은 전체 루프를 다루었습니다:
- 첫 번째 가져오기(First import). Gemma의 제안이 사전 선택된 답변 없이 도착했습니다. 저는 열과 가정을 확인하고, 계산 추적을 검사했으며, 승인하고, CSV/XLSX 내보내기를 다운로드했습니다.
- 다음 주 목록(Next week's list). 동일 공급업체의 저장된 계획이 재사용되었습니다. Pricor는 가격 열이 이동했음을 감지하고 잘못된 열을 읽는 대신 저장된 매핑을 따랐습니다.
- 가격 보고서(Price report). 두 개의 승인된 목록을 비교한 결과: 1,057개와 1,060개의 수락된 제품에 걸쳐 514개 증가, 118개 감소, 420개 변동 없음, 8개 신제품, 5개 단종, 그리고 +200%의 이상치 하나가 있었습니다.
이 실행에 대해 명확히 하자면: 두 번째 목록은 실제 목록을 교란하고 매니페스트(manifest)에 모든 변경 사항을 기록하는 도구로 시뮬레이션되었습니다. 내보내기는 단순히 총계뿐만 아니라, 그 매니페스트의 모든 변경된 가격과 추가되거나 제거된 코드를 기준으로 확인되었습니다. 이것은 동작 방식을 보여줄 뿐이며, 실제 공급업체의 가격 업데이트 증거는 아닙니다.
상단의 예시 뒤에 있는 산술 계산은 데모에서 영수증이 보여주듯이 다음과 같습니다:
12,100.00 개당, 21% VAT 포함
× 0.90 × 0.95 10%와 5%의 연쇄 할인율
÷ 1.21 VAT 제거
...
어떤 모델도 그 결과를 계산하지 않습니다.
코드
리포지토리: github.com/teofurlan/pricor
이 애플리케이션은 Apache-2.0 라이선스입니다: 백엔드, 프론트엔드, 테스트, 코퍼스 다운로드 도구(공급업체 파일은 재배포되지 않고 공개 페이지에서 연결됨), Laya 파인튜닝 도구 및 벤치마크. 파인튜닝된 Laya 가중치는 아직 게시되지 않았으며, 학습 스크립트가 이를 재생성합니다.
구축 방법(How I Built It)
전체 설계는 하나의 규칙을 따릅니다: 모델이 해석하고, 엔진이 실행하며, 사용자가 결정합니다.
스프레드시트 → 결정론적 프로필 → Laya → Gemma → 사용자 확인
↓
검증된 운영 계획
...
- **결정론적 프로파일러(deterministic profiler)**가 헤더 행, 행 유형, 명시적인 열 유형 및 숫자 형식을 찾습니다 (
12.100,00은 12.1이 아님).
- 미세 조정된 다국어 Laya 모델이 각 열을 폐쇄된 역할 집합으로 분류합니다. 확신하지 못하는 열은 Ollama를 통해 로컬에서 실행되는 Gemma 4 E4B로 전달됩니다. 여전히 불확실한 것은 사용자에게 넘어갑니다.
- 질문은 계획(plan)이 빠진 부분으로부터 결정론적으로 생성됩니다: 가격에 부가세(VAT)가 포함되어 있나요? 팩당인가 단위당인가요? 어떤 통화인가요? Gemma는 답변을 제안하고 (보정되지 않은) 신뢰도를 보여줄 수 있지만, 사용자 대신 무언가를 승인하지는 않습니다.
- 실행기(executor)는 **폐쇄된 작업 목록(closed catalog of operations)**만을 이해합니다: 숫자를 구문 분석하고, 체인 할인(chained discounts)을 적용하고, 부가세(VAT)를 제거하고, 팩을 단위로 분할하고, 값을 매핑하고, 텍스트를 정규화하며, 선택적으로 통화를 변환하는 것입니다. 모델이 생성한 코드를 절대 실행하지 않습니다.
- FastAPI가 React 프런트엔드에 서비스를 제공합니다. SQLite는 승인된 계획 버전과 가격 기록을 저장합니다. 내보내기(Exports)에는 별도의 거부된 행 보고서가 포함됩니다.
계획은 공급업체별로 저장됩니다. 재사용하기 전에, 지문 인식(fingerprint) 기능이 각 열의 위치, 정규화된 헤더 및 명시적인 유형을 확인합니다. 계획에 필요한 열이 누락되면, 비용을 조용히 생성하는 대신 영향을 받는 규칙이 차단됩니다. 통화 변환은 원래 값과 통화를 유지하고 해당 실행에 사용된 환율을 스냅샷으로 저장합니다.
메모리는 명시적이고, 로컬이며, 인용됨
Pricor는
메모리(memory)는 사용자가 직접 작성하거나 Gemma의 제안을 수락하고 **'기억하기(Remember this)'**를 클릭할 때만 생성됩니다. 절대로 자동으로 생성되지 않습니다. 가장 관련성 높은 메모리는 번호가 매겨진 신뢰할 수 없는 데이터로 Gemma에 전달되며, Gemma는 제안이 의존하는 모든 메모리를 인용해야 하고 UI에는 해당 인용이 표시됩니다. 공급된 메모리를 인용한 답변은 완전히 폐기됩니다. 메모리가 사용 불가능하면, 알림과 함께 가져오기(import)가 계속 진행됩니다. '메모리' 패널을 통해 사용자는 모든 것을 보고, 편집하고, 삭제할 수 있습니다.
수동으로 확인하는 경우(벤치마크 아님), 공급업체 한 곳에 대한 메모 하나와 다른 공급업체에 대한 메모 하나를 사용하여 Gemma의 VAT 및 개별 포장 단위당 제안은 다른 쪽이 아닌 올바른 메모리를 인용했습니다.
벤치마크가 모든 것을 자동화할 근거가 되지 못했다
리포지토리(repo)의 벤치마크는 두 유통업체에서 가져온 단 29개의 제외된 열만을 포함하고 있으므로, 리더보드가 아닌 건전성 점검(sanity check)으로 읽어주십시오:
| 분류기 | 정답 수 | 정확도 |
|---|
| 키워드/유형 휴리스틱스 (Keyword/type heuristics) | 24/29 | 82.8% |
| ... | | |
전체 시트 순차 처리 과정에서, 11개의 열이 자동으로 해결되었고(10개 정확), 29개 중 18개가 여전히 사용자에게 전달되었습니다. 이것이 정직한 결과입니다: 이 데이터에 대해서는 앱이 질문해야 합니다. Laya는 GPU당 열당 약 35ms가 걸렸고, Gemma는 시트당 평균 약 11.4초(RTX 4060 Laptop GPU)가 걸렸습니다. 이것들은 속도 향상 주장으로 볼 수 없는 다른 단위입니다.
주의사항은 숫자 옆에 문서화되어 있습니다: 임계값(thresholds)은 부분적으로 합성된 검증 데이터로 선택되었고, 테스트 세트에는 일부 역할이 부족하며, 휴리스틱스는 제외된 헤더를 본 후에 작성되었고, 첫 실행 후 두 개의 레이블이 수정되었습니다. 이후 제가 시도한 휴리스틱스+Laya 합의 단계는 이점을 보여주지 못했으므로, 배포된 순차 처리 과정에서는 이를 사용하지 않습니다.
알려줄 가치가 있는 버그들
백분율(Percentages). Excel은 21%로 표시되는 셀을 0.21로 저장합니다. 숫자 형식을 잃으면 21% VAT가 0.21% VAT가 되어 여전히 그럴듯해 보이는 비용이 산출됩니다. 이제 독자는 엔진의 백분율 포인트 계약에 손대지 않고도 스프레드시트 경계에서 이 의미를 유지할 수 있습니다.
두 번째 할인 열(A second discount column). 데모를 녹화하는 동안, 목록에 두 개의 별도 할인 열이 있는 경우 플랜 빌더가 첫 번째 것만 사용한다는 것을 발견했습니다. 한 셀 내의 연결된 할인(10+5)은 올바르게 처리되며, 이는 데모에서 사용하는 방식이지만, 두 번째 열은 현재 아무런 경고 없이 무시되어 앱 자체의 "조용히 아무것도 하지 않는다"는 규칙을 깨뜨립니다. 이를 수정하는 것이 다음 목록에 있습니다.
백엔드 스위트는 1,219개의 테스트 (세 개의 라이브 모델 테스트 제외)를 통과하고 프론트엔드 스위트는 194개를 통과했습니다. 이 중 어느 것도 공급업체가 생성할 수 있는 모든 Excel 파일과의 호환성을 증명하지는 못합니다.
Open Innovation이 중요한 이유
오픈 모델은 채팅봇처럼 덧붙여진 것이 아니라 가져오는 경로(import path)에 있습니다. Laya와 Gemma는 공급업체의 데이터를 읽고, 그 한계가 앱이 사용자에게 질문해야 하는 시점을 직접 결정합니다.
- 개인정보 보호(Privacy). 상점의 공급업체 목록과 비용은 호스팅된 모델 제공자에게 도달하기 위해 자체 컴퓨터를 벗어나지 않습니다.
- 비용(Cost). 상점은 매주 동일한 공급업체를 가져옵니다. 로컬 추론(local inference)을 사용하면 플랜을 재사용할 때 호출당 비용이 발생하지 않습니다. 전기, 하드웨어 및 지원이 공짜는 아니지만, 돌아가는 계량기가 없습니다.
- 오프라인(Offline). 인터넷 없이도 가져오기 작업이 가능합니다. 선택적인 온라인 환율 업데이트만 연결이 필요하며, 캐시된 값이나 수동으로 입력한 환율은 오프라인에서도 작동합니다.
- 미세 조정 및 측정(Fine-tuning and measurement). 오픈 가중치(Open weights) 덕분에 저는 이 좁은 작업에 맞춰 Laya를 미세 조정하고 제가 측정한 방식을 정확하게 공개할 수 있었습니다. 이는 제로샷 기준선(zero-shot baseline)보다 개선되었지만, 이 작은 세트에서는 Gemma나 휴리스틱스(heuristics)를 능가하지 못했으며, 모든 것이 검사 및 교체가 가능하기 때문에 그렇게 말할 수 있습니다.
- 라이선싱(Licensing). Gemma 4는 Apache-2.0이므로, 상점이 비용을 지불하는 무언가 안에 탑재될 수 있습니다.
다음 단계는 더 많은 기능 추가가 아닙니다. 이미 처리하고 있는 목록을 사용하여 소매업체와 감독된 파일럿 테스트를 진행하는 것입니다. Pricor의 결과물을 현재 가져오는 데이터(import)와 비교해 보고, 어떤 확인 절차에서 혼란스러워 하는지 관찰하며, 지속적인 지원 중 무엇에 비용을 지불할 가치가 있을지 문의해야 합니다. 그때까지는 사업 사례가 잠정적 상태로 유지됩니다.
수상 부문 (Prize Categories)
- Gemma의 최적 활용: Gemma 4 E4B를 Ollama를 통해 로컬에서 실행합니다. 이 모델은 Laya가 확신하지 못하는 열(column)들을 분류하고, 계획 질문에 대한 답변을 제안하며, 명시적으로 저장된 메모리를 인용 출처로 사용합니다. 이 모델은 미세 조정(fine-tuned)되지 않았으며, 가격 산술 계산은 절대 수행하지 않습니다.
저는 전체 수상 부문에도 참가합니다. 다른 파트너 통합에 대한 주장은 하지 않습니다.