90 Commits

Author SHA1 Message Date
Hyungi Ahn 3e831a2dc7 fix(canonical): Phase 1D script sys.path — /app/scripts/.. 가 PYTHONPATH 루트
fastapi 컨테이너는 WORKDIR=/app, 코드가 직접 풀려있고 app/ 디렉토리 없음.
backfill_category.py 의 ../app 패턴은 컨테이너 안에서 /app/app (없음)
가 되어 ModuleNotFoundError. 스크립트 자기 디렉토리의 .. 를 sys.path 에
넣어 /app 루트 노출.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:50:23 +09:00
Hyungi Ahn f98cf2e505 ops(canonical): Phase 1D marker pilot one-shot script (select/enqueue/report)
30건 한정 stratified pilot. baseline markdown 품질 측정 후 Phase 2 전체
백필 결정. 영구 worker 경로 아님.

대상 WHERE:
  deleted_at IS NULL
  AND file_format='pdf'
  AND md_status='pending'
  AND category='document'
  AND document_type NOT IN SKIP_DOC_TYPES (marker_worker 와 일관)

Stratification:
  ai_domain × file_size_bucket (small<500KB / medium<5MB / large)
  documents 에 page_count 컬럼 부재 (marker_worker 가 PyMuPDF 로 동적
  측정) → file_size 를 길이 proxy 로 사용.
  cell 안에서 file_size 작은/큰 mix 로 짧은/긴 문서 차이 관찰.

Subcommands:
  select  — 30건 dry-run + JSON 저장 (/tmp/phase1d_pilot.json)
  enqueue — markdown 큐 enqueue (uq_queue_active 충돌 시 skip)
  report  — md_status / 평균 elapsed / 실패 top5 / heading anchor 후보 /
           KaTeX 후보 / file_size bucket 별 success 비율 / UI 검수 URL

리포트 메모:
  markdown_image_count 는 현재 server.py 가 _images 버림 → 0 정상.
  Phase 1B.5 에서 _images 출력 시 자동 활성.

실행:
  docker compose exec fastapi python /app/scripts/phase1d_pilot.py select
  docker compose exec fastapi python /app/scripts/phase1d_pilot.py enqueue --yes
  docker compose exec fastapi python /app/scripts/phase1d_pilot.py report

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:49:17 +09:00
Hyungi Ahn 5404343a1a fix(study): HC-5 block math spacing — KaTeX \$\$...\$\$ 앞뒤 빈 줄 보장 자동 fix
문제: 보기/해설 본문의 \$\$ ... \$\$ block math 가 앞뒤 빈 줄 없으면
마크다운 파서가 라벨/텍스트와 같은 단락에 묶어 KaTeX 렌더 실패 → raw 표시.

운영 결과 (21회분 = 2,100문항):
- HC-5 detect 317건 모두 자동 fix 완료. 모든 회차 재검사 0건.
- 추가 fix: q1579 (2023년 1회 q81) 바이메탈 ASCII 다이어그램 fence wrap.

알고리즘:
- 자체 줄 \$\$...\$\$ (한 줄 안 시작·종료, 길이 4+) detect.
- 앞·뒤 라인이 비어있지 않으면 빈 줄 삽입 — idempotent.
- inline \$ ... \$ 영향 없음.
- 의미 변경 0 (빈 줄 삽입만, 본문 텍스트/수식 보존).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:29:39 +09:00
Hyungi Ahn 87b6c38d99 feat(study): 보기 동그라미 숫자 (①②③④) 형식 지원 + 10회분 추가
운영 중 발견 — 2023년 이후 회차 md 가 보기를 ①②③④ 으로 표기.
파서가 "1번:" / "1." / "1)" 만 매칭해서 100문항 보기 1~4번 비어있음 → import abort.
CIRCLED 매핑 활용해서 동그라미 숫자도 처리 추가.

운영 결과 (10회분 추가, 누락 png 제외):
- 2022년 3회 / 2023년 1회: 100건 (이미지 0)
- 2023년 2회: 98건 / 2023년 3회: 96건 (png 일부 누락)
- 2024년 1·2·3회: 각 98건 (png 누락)
- 2025년 1·2·3회: 97/99/97건 (png 누락)
- audit: HC 0 / LC-5 1건 자동 fix (q2183 표 구분자)
- 누락 png 19건은 사용자 추후 보충 예정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 06:47:06 +09:00
Hyungi Ahn 1d73986fd6 feat(study): 가스기사 import 스크립트 — 보기 형식 다양화 + subject 슬래시 정규화
운영 중 발견한 패턴 추가:
- 보기 형식: "1번:" + "1." + "1)" 모두 매칭 (2022년 회차에서 "1." 사용 발견).
- subject 정규화: 괄호 형태(연소공학 (열역학))뿐 아니라 슬래시 형태
  (가스안전관리 / 가스설비) 도 head + scope 분리.

운영 결과 (6회분 = 600문항 추가):
- 2020년 3회 / 2021년 1·2·3회 / 2022년 1·2회 모두 등록 완료.
- 이미지 27건 자동 첨부 (1+4+7+6+5+4).
- audit: HC 0건, LC-5 2건 (2022년 2회 q41/q90 표 구분자 누락) 자동 fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:56:17 +09:00
Hyungi Ahn cb07ffa4ce feat(study): study_questions DB 마크다운 정합성 audit 스크립트
scripts/audit_study_question_markdown.py:
- HC 자동 fix (HC-1 outer fence / HC-2 escape 잔재 / HC-3 HTML 엔티티 / HC-4 공백)
  · HC-2 KaTeX 명령어 (\rho, \nabla 등) false positive 회피 — lookahead (?![A-Za-z])
  · 비정상 카운트 abort_threshold 안전장치
- LC 리포트 (LC-1 백틱 / LC-2 \$\$ / LC-3 \$ / LC-4 ** / LC-5 표 / LC-6 들여쓰기)
  · 각 항목에 edit 페이지 URL 포함 — 사용자 직접 처리 가능
  · LC-5 다컬럼 표만 검사 (|...|y|... pipe 3+) — 절대값 |x| 한컬럼 false positive 회피

운영 결과 (5회분 = 500문항):
- 2019년 1회: HC-4 43건 + LC-1 8건 + LC-3 2건 + LC-6 3건 자동/사용자 fix
- 2019년 2회: LC-1 4건 자동 fix
- 2019년 3회 / 2020년 1·2회: 0건
- 모두 audit PASS (HC 0 / LC 0)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:06:39 +09:00
Hyungi Ahn b20c4f933b feat(study): exam_round 필터 + 일괄 import 스크립트 — 1천+ 문제 대비 (P0)
문제: 1천+ 문항 토픽에서 보기 페이지 prev/next 가 page_size=200 cap 으로
회차 외 문항만 받아 같은 회차 prev/next 누락 회귀.

해결:
- /study-topics/{tid}/questions 에 exam_round Query 파라미터 추가 (exact match).
- StudyQuestionSummary 응답에 exam_question_number 필드 추가.
- exam_round 필터 시 정렬 = exam_question_number asc NULLS LAST, created_at asc.
- 보기 페이지 loadRoundSiblings 가 ?exam_round= 로 한 회차만 fetch.
- 토스트 문구 "토픽 200문제 초과" → "이 회차에 200문항 초과" (의미 일치).

추가 — 가스기사 기출 일괄 import 스크립트:
- scripts/import_gas_questions.py: md 파서 + dry-run + apply.
  · exam_question_number 3소스 (파일명/제목/메타) 일치 검증.
  · subject 정규화 (괄호 세부분류는 scope 로 이동, 5과목 통일).
  · 이미지 4케이스 판정 + import_reports/{회차}_image_required.md 생성.
  · 첫 실패 abort 기본, --skip-existing/--continue-on-error 옵션.
  · 토큰 사전 검사 (GET /study-topics/{tid}).
- import_reports/: 2019년 1~3회 + 2020년 1~2회 리포트.
- 운영: 4회분 360문항 자동 import 완료 (이미지 4건 자동 첨부).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:39:02 +09:00
Hyungi Ahn 373dd059b7 fix(study): outer fenced code block auto-unwrap (renderMathMarkdown + DB 일괄 정리)
AI 응답이 마크다운 자체를 \`\`\` 으로 감싸서 오는 패턴 (시작만 있고 닫음 누락 포함)
때문에 explanation/AI 해설 영역이 raw 코드블록으로 보이는 회귀.

- frontend/lib/utils/mathMarkdown.ts: stripOuterFence helper.
  - terminated wrap 처리 (inner 에 \`\`\` 추가 있으면 보존)
  - unterminated 처리 (백틱 그룹 == 1 인 경우만 안전하게 unwrap)
  - 본문 중간 정상 코드블록은 보존
- scripts/strip_outer_fences.py: dry-run + --apply 양 모드.
  - 5개 필드 (question_text, choice_1~4, explanation, ai_explanation, content) 검사.
  - 운영 결과 explanation 34건 unwrap 적용 완료, recount 0 검증.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:55:18 +09:00
Hyungi Ahn 95bcdb851b fix(ops): backfill 쿼리에 빈 extracted_text 제외 — 무한 retry 방지
3일 운영 결과 doc 4811, 5181 가 extracted_text='' (빈 문자열) 인데
IS NOT NULL 만 걸려 enqueue → classify_worker 의 not doc.extracted_text
truthy 체크에서 ValueError → max_attempts(3) 도달 → status=failed.
다음 backfill 사이클에서 다시 enqueue 되어 12회 반복, failed 24건 누적.

수정: tier_backfill.py + backfill_tier.py 양쪽 SQL 에
LENGTH(extracted_text) > 0 추가. 빈 문자열 문서는 enqueue 자체에서 제외.

기존 failed 24건 정리 SQL (사용자가 수동 실행):
  DELETE FROM processing_queue
  WHERE stage='classify' AND status='failed'
    AND error_message LIKE '%extracted_text%';

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:25:12 +09:00
Hyungi Ahn 8427ac886c feat(memo): sync content ↔ memo_task_state on create/update + backfill script
본문에 `- [x]` 로 직접 입력된 체크 항목도 checked_at 가 기록되어 10초 후
자동 숨김 대상이 되도록 create_memo / update_memo 에 sync 로직 추가.

- _sync_task_state_with_content: - [x] 에 checked_at 없으면 현재 시각으로 기록,
  - [ ] 또는 사라진 index 는 state 에서 정리
- scripts/backfill_memo_task_state.py: 배포 이전 기존 노트에 현재 시각 backfill
  (docker compose exec fastapi python /app/scripts/backfill_memo_task_state.py --apply)
2026-04-24 15:40:18 +09:00
Hyungi Ahn 814882a0fe feat(ops): tier triage 레거시 백필 스크립트
PR-B B-1 배포 이전에 classify 된 6770건 레거시 문서에 대해 ai_tldr /
ai_bullets / ai_detail_summary 등 tier 산출물을 채우기 위한 백필 도구.

사용:
  docker exec hyungi_document_server-fastapi-1 \
    python /app/scripts/backfill_tier.py --domain safety --limit 50 --dry-run
  docker exec hyungi_document_server-fastapi-1 \
    python /app/scripts/backfill_tier.py --domain safety --limit 50 --apply

도메인 필터: safety / law / manual / news / drive_sync / memo

ORDER BY created_at DESC 로 최신 우선. ON CONFLICT DO NOTHING 이라
기존 pending/processing 행 있으면 중복 enqueue 방지.

MLX 26B 단일 Semaphore 경로라 처리 속도 ~1건/분. 50건 ≈ 1시간.
대량 백필은 야간 분할 권장. 이번 세션 Industrial_Safety 50건이
첫 smoke 대상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:30:31 +09:00
Hyungi Ahn e88640d3d8 feat(category): law 카테고리 분리 — enum + backfill + classify skip
- migrations/152: ALTER TYPE doc_category ADD VALUE 'law' (DDL only; PG16 단일-트랜잭션 제약상 backfill 은 별도)
- models/document.py: Enum 에 'law' 추가 (7 활성 + 3 유보)
- workers/law_monitor.py: Document(..., category='law') — 신규 유입부터 세팅
- workers/classify_worker.py: source_channel='law_monitor' early-return + 최소 필드 (ai_domain='법령', ai_tags=['법령'], importance='medium'). AI classify skip — 법령 구조 고정/외부 source of truth/자동 재수집
- scripts/backfill_category.py: law 분기 + WHERE re-target ((source_channel='law_monitor' AND category='document')) + VERIFY cat_law/law_source_count + fail 조건
- api/documents.py: default 목록 제외에 law_monitor 추가 (news 와 동일 패턴)
- api/dashboard.py: documents count FILTER 에 law_monitor 제외 (category_counts.law 는 기존 GROUP BY category 로 자동 노출)
- frontend/Sidebar.svelte: '법령 알림' 버튼 ?source=law_monitor → ?category=law (explicit category 경로가 default exclusion 을 skip)

plan: ~/.claude/plans/stateless-churning-raccoon.md
axis 원칙: category=UI 축, policy/telemetry=source_channel+ai_domain 축 (feedback_category_vs_ai_domain_axis.md)

배포 순서: push → GPU pull → compose up --build fastapi frontend → backfill --dry-run → --apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 09:14:56 +09:00
Hyungi Ahn 8fdea88676 feat(documents): §1 category enum + ai_suggestion 승인 파이프
plan: ~/.claude/plans/luminous-sprouting-hamster.md §1

- migrations/143_category.sql: doc_category enum (6 활성 + 3 유보) +
  documents.category + documents.ai_suggestion JSONB + 2 idx.
- app/models/document.py: category (Enum, create_type=False), ai_suggestion (JSONB).
- app/prompts/classify.txt: document_type enum 에 7 실무 doctype 추가
  (발주서/세금계산서/명세표/도면/증명서/계획서/시방서) + facet_doctype
  필드 directive.
- config.yaml: document_types 에 7 항목 추가 (worker 검증 통과).
- app/workers/classify_worker.py: FACET_DOCTYPES / LIBRARY_SUGGESTION_DOCTYPES
  상수, facet_doctype 파싱(기존값 미덮어씀), 발주서/세금계산서/명세표
  감지 시 ai_suggestion={proposed_category=library, proposed_path=@library/
  거래/{YYYY}/{doctype}, source_updated_at=doc.updated_at.isoformat(), ...}.
  category / user_tags 자동 전이 금지 (suggestion-only).
- app/api/documents.py:
  · DocumentResponse 에 category / ai_suggestion 노출
  · GET /documents ?category=<cat> / ?has_suggestion / ?proposed_category
    (category 지정 시 기본 news/memo 제외 해제 — §2 승인 UI 계약)
  · GET /documents/library 를 Document.category=='library' 기반으로 재구현
    (path subquery 는 user_tags 유지 — 분류 내부 서가 경로)
  · POST /documents/{id}/accept-suggestion — FOR UPDATE + idempotent no-op +
    dual 409 stale (payload source_updated_at / documents.updated_at) +
    user_tags idempotent append
  · DELETE /documents/{id}/suggestion — idempotent, stale 검사 없음
- scripts/backfill_category.py: dry-run / apply. 매핑(news/memo/@library/else)
  + 3-way 상대 검증 (all_rows==categorized, uncategorized==0,
  cat_library==has_library_tag — 자동 전이 금지 정책 검증).

남은 DoD (원격 배포 후): docker compose up → migration 143 적용 → backfill
apply → smoke (drive_sync 발주서 업로드 suggestion 생성 / category 유지,
accept-suggestion idempotency + 409 stale 두 벡터, /documents?category=library
== /documents/library 건수 일치).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:32:01 +09:00
Hyungi Ahn c9f766512d feat(eval): run_eval_ask runner 에 X-Eval-Token/X-Eval-Case-Id 전파 추가
배경: Phase 3.5 fix2 로 서버 /ask 는 X-Source=eval 을 받아들이려면
X-Eval-Token 이 EVAL_RUNNER_TOKEN 와 일치해야 함. runner 에 해당 헤더
주입 경로가 없어 eval 호출이 전부 source='document_server' 로 강등됐음.

변경:
- call_ask / call_analyze: eval_token, eval_case_id 인자 추가. 조건부 헤더 주입
- run_eval: eval_token 파라미터 추가
- CLI: --eval-token 플래그 추가 (env EVAL_RUNNER_TOKEN 자동 fallback)
- main(): --source=eval + --eval-token 미지정 조합에 warning 출력
- eval_case_id 는 item id 자동 전달 → ask_events.eval_case_id join 키로 활용

E.6 재측정의 source='eval' 정확 기록 선결 조건.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 09:12:24 +09:00
Hyungi Ahn c82d52e73f feat(eval): E.6 runner + 평가셋 main 복원 (from feat/eval-infra)
selective checkout (not cherry-pick):
- scripts/run_eval_ask.py (RESULT_FIELDS 21 고정, X-Source:eval 헤더)
- evals/ask_analyze_v1.jsonl (300 case = ask 220 + analyze 80)

E.3/E.6 측정 진입점. feat/eval-infra 의 원본은 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 09:10:18 +09:00
Hyungi Ahn 3780c10f39 fix(scripts): verify_upload_size — processing_queue 컬럼 doc_id → document_id 2026-04-17 08:26:08 +09:00
Hyungi Ahn 2f68fd4196 fix(scripts): verify_upload_size case 6 — CL override 대신 실제 body 크기로 슬랙 초과 테스트
httpx 의 h11 레이어가 Content-Length 와 body 길이 불일치를 client-side 에서
LocalProtocolError 로 거절해서, CL 헤더만 override 해 서버 pre-check 경로를
외부에서 격리 테스트하는 것이 불가능했음. 대신 body 자체가 slack 임계치를
초과하는 케이스로 변경 — multipart CL 이 자동으로 `max_bytes * slack_ratio`
를 넘어 서버 pre-check 가 먼저 catch 함.

또한 기존 case 7 (CL 위조) 는 같은 이유로 실현 불가능해 제거. 5 케이스에서
6 케이스로 조정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:25:30 +09:00
Hyungi Ahn da30eca829 feat(scripts): upload size 경계 검증 스크립트 (수동 on-demand)
Phase B 의 스트리밍 size 검증을 외부에서 확인할 수 있는 스크립트.
pytest 인프라가 Phase 0 상태이므로 full test harness 구축을 미루고,
`scripts/verify_upload_size.py` 단일 파일로 경계 케이스를 즉시 회귀 검증.

7 케이스:
- 0 bytes → 400 (정책)
- 1 byte → 201 (happy path)
- max_bytes - 1 → 201 (경계 하)
- max_bytes 정확 → 201 (경계 상)
- max_bytes + 1 → 413 (스트리밍 차단)
- CL slack 초과 (override 헤더) → 413 (사전 차단)
- CL 위조 (작은 헤더 + 큰 body) → best-effort (서버 거절 status 수용)

`/api/config/public` 에서 max_bytes 를 동적 획득. slack_ratio 는 비공개라
스크립트 상수로 1.05 하드코딩 (config.yaml 과 동기화 유지 주석 명시).

Cleanup: 파일명 prefix `__upload_boundary_test__` + ns timestamp 로
실데이터와 격리. 시작 시 pre-cleanup + 각 케이스 직후 + finally 블록 cleanup.

`docker compose exec fastapi python /app/scripts/verify_upload_size.py` 로 실행.
UPLOAD_TEST_TOKEN + DATABASE_URL 환경 변수 필요. scripts/ 는 이미 read-only
volume 으로 마운트돼 있어 배포·재빌드 불필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:23:20 +09:00
Hyungi Ahn 3a3cd832f6 fix(scripts): calibrate_ask.py --since/--until datetime 파싱
asyncpg 이 TIMESTAMPTZ 파라미터에 문자열 대신 datetime 객체를 요구
(DataError: invalid input, expected datetime instance, got str).
argparse type=datetime.fromisoformat 로 CLI 단계에서 파싱.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:22:01 +09:00
Hyungi Ahn 1c502328f1 fix(scripts): calibrate_ask.py None 파라미터 타입 추론 실패 해소
asyncpg 이 $N IS NULL 비교에서 Python None 의 타입 추론 실패
(AmbiguousParameterError: could not determine data type of parameter).
None 인 조건은 WHERE 에서 아예 제외 — clauses 동적 조립.
부수 효과: 조건 0개일 때 "TRUE" 반환으로 quiet fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:21:29 +09:00
Hyungi Ahn 0ab1f888fa fix(scripts): calibrate_ask.py SQL ::timestamptz cast 파싱 충돌 해소
SQLAlchemy text() 의 `:name` 파라미터가 PostgreSQL `::type` cast 와
토큰 경계 충돌로 치환되지 않아 `syntax error at or near ":"` 발생.
`:since::timestamptz` → `CAST(:since AS TIMESTAMPTZ)` 로 변경.

Reproduction: --since/--until 옵션 사용 시 모든 집계 쿼리 실패.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:20:46 +09:00
Hyungi Ahn 99abd287dc feat(scripts): Phase 3.5 — calibrate_ask.py CLI (Q0~Q8 + render + FP CSV)
scripts/calibrate_ask.py — ask_events 집계 + markdown report 영구 도구.

기능:
- argparse: --source / --prompt-version / --since / --until / --eval-split
  (tuning|confirm|all, id 해시 기반 deterministic split) / --run-label /
  --output / --format md|json / --compare-against / --sample-limit /
  --fp-artifacts / --inspect-shape / --dry-run
- 9개 fetcher (모두 read-only SELECT):
  - Q0 defense_layers shape inspect
  - Q1 re-gate tier 분포
  - Q2 max_rerank_score 히스토그램 (bucket × bin)
  - Q3 classifier 혼동행렬
  - Q4 verifier severity 분포 (cast + COALESCE NULL safe)
  - Q5 hallucination_flags top-K (UNION ALL outer wrap, strong/weak 컬럼 유지)
  - Q6 eval golden mismatch (eval_case_id 기반 join + query string fallback)
  - Q7 FP candidate (case A/B/C 분리 + candidate_reason 컬럼 + LIMIT/3 분배)
  - Q8 answer_length p25/p50/p75 분포 (E.3 v1↔v2 비교 축)
- markdown render + json baseline + delta compare (compare-against)
- FP CSV dump (artifacts/fp_candidates_{run_label}.csv) + is_true_fp 공란
- dry-run: tests/calibrate_fixtures/sample_ask_events.json 로 출력 검증
- --threshold-overrides: Step 0 feasibility 통과 후 v2 (현재 stub raise)

read-only 강제: INSERT/UPDATE/DELETE/ALTER/DROP/TRUNCATE 0건.

tests/calibrate_fixtures/sample_ask_events.json: dry-run snapshot fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:11:06 +09:00
Hyungi Ahn 7cdeac20cf fix: update migration script to read .dtBase2/Files.noindex directly
Instead of requiring DEVONthink export, reads files directly from
.dtBase2 bundle's Files.noindex/ directory structure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 08:17:44 +09:00
Hyungi Ahn 299fac3904 feat: implement Phase 1 data pipeline and migration
- Implement kordoc /parse endpoint (HWP/HWPX/PDF via kordoc lib,
  text files direct read, images flagged for OCR)
- Add queue consumer with APScheduler (1min interval, stage chaining
  extract→classify→embed, stale item recovery, retry logic)
- Add extract worker (kordoc HTTP call + direct text read)
- Add classify worker (Qwen3.5 AI classification with think-tag
  stripping and robust JSON extraction from AI responses)
- Add embed worker (GPU server nomic-embed-text, graceful failure)
- Add DEVONthink migration script with folder mapping for 16 DBs,
  dry-run mode, batch commits, and idempotent file_path UNIQUE
- Enhance ai/client.py with strip_thinking() and parse_json_response()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:35:36 +09:00
Hyungi Ahn a601991f48 feat: implement Phase 0 auth system, setup wizard, and Docker config
- Add users table to migration, User ORM model
- Implement JWT+TOTP auth API (login, refresh, me, change-password)
- Add first-run setup wizard with rate-limited admin creation,
  TOTP QR enrollment (secret saved only after verification), and
  NAS path verification — served as Jinja2 single-page HTML
- Add setup redirect middleware (bypasses /health, /docs, /openapi.json)
- Mount config.yaml, scripts, logs volumes in docker-compose
- Route API vs frontend traffic in Caddyfile
- Include admin seed script as CLI fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:21:45 +09:00
Hyungi Ahn e48b6a2bb4 chore: remove v1 files from main branch
v1 코드는 v1-archive 브랜치 + v1-final 태그로 보존.
필요시 git show v1-final:<파일경로>로 참조 가능.

삭제: applescript/, launchd/, v1 scripts, v1 docs, requirements.txt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 09:35:09 +09:00
Hyungi Ahn 35062145ed fix(law_monitor): US 타입 필터 제거 + JP RDF 네임스페이스 수정
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:52:13 +09:00
Hyungi Ahn c8e30b530b fix: AppleScript POSIX path 변수 방식 + 단일 -e 실행으로 따옴표 문제 해결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:16:23 +09:00
Hyungi Ahn f13b998bbc fix: AppleScript 행별 -e 분할 실행 — 파일 방식 인코딩 문제 회피
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:13:48 +09:00
Hyungi Ahn 735c0722f4 fix: AppleScript를 임시 파일로 실행 — osascript -e 이스케이프 문제 해결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:10:32 +09:00
Hyungi Ahn 446963cfae fix(law_monitor): AppleScript f-string 제거 + EU 파일명 고유화
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:09:10 +09:00
Hyungi Ahn 0b950a4033 fix(law_monitor): AppleScript 따옴표 이스케이프 수정
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:04:34 +09:00
Hyungi Ahn 6a44b10a3b fix(law_monitor): JP/EU RSS URL 수정 — news.rdf + rss.xml, RDF 네임스페이스 대응
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:03:39 +09:00
Hyungi Ahn 9dc0694035 feat(law_monitor): 외국 법령 지원 추가 — US OSHA, JP 厚労省(MLX 번역), EU-OSHA
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:54:14 +09:00
Hyungi Ahn ec6074d9ee fix(law_monitor): API 에러 응답 로깅 추가 — 인증 실패 시 조용히 넘어가던 문제
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:48:22 +09:00
Hyungi Ahn aca4a027ba fix: LLM thinking 허용 + 마지막 유효 JSON 추출 방식으로 변경
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:39:05 +09:00
Hyungi Ahn 49c39a182b fix: LLM thinking 출력 대응 — max_tokens 증가 + JSON 추출 강화
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:34:20 +09:00
Hyungi Ahn 948be1624f fix: Qwen3.5 /nothink 모드 + json_mode 파라미터 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:33:23 +09:00
Hyungi Ahn a77477140b fix: MLX 서버(localhost:8800) 대응 — Ollama API → OpenAI 호환 변경
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:31:52 +09:00
Hyungi Ahn 084d3a8c63 feat: 전체 PKM 스크립트 일괄 작성 — 분류/법령/메일/다이제스트/임베딩
- scripts/pkm_utils.py: 공통 유틸 (로거, dotenv, osascript 래퍼)
- scripts/prompts/classify_document.txt: Ollama 분류 프롬프트
- applescript/auto_classify.scpt: Inbox → AI 분류 → DB 이동
- applescript/omnifocus_sync.scpt: Projects → OmniFocus 작업 생성
- scripts/law_monitor.py: 법령 변경 모니터링 + DEVONthink 임포트
- scripts/mailplus_archive.py: MailPlus IMAP → Archive DB
- scripts/pkm_daily_digest.py: 일일 다이제스트 + OmniFocus 액션
- scripts/embed_to_chroma.py: GPU 서버 벡터 임베딩 → ChromaDB
- launchd/*.plist: 3개 스케줄 (07:00, 07:00+18:00, 20:00)
- docs/deploy.md: Mac mini 배포 가이드
- docs/devonagent-setup.md: 검색 세트 9종 설정 가이드
- tests/test_classify.py: 5종 문서 분류 테스트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:32:36 +09:00