Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cedc6e9
검색 방법을 다듬습니다
wagurano Jul 18, 2026
a4beb24
직업과 신체 부위로 메인 검색 화면을 새로 만들고 과거 방식 검색은 /search로 옮깁니다
wagurano Jul 18, 2026
4912d7c
루비 버전 관리를 asdf .tool-versions로 옮깁니다
wagurano Sep 2, 2026
ae0ea94
직업·신체부위 검색용 데이터 로드 방법을 README에 추가합니다
wagurano Sep 2, 2026
7d18c97
로컬 dev용 overmind를 gem으로 추가합니다
wagurano Sep 2, 2026
a37e275
부담 신체 부위 체크박스를 상위 12개로 줄이고 나머지는 자동완성으로 찾습니다
wagurano Sep 2, 2026
791359e
목록의 사망 배지를 "사망"으로 줄이고 음영 표시를 없앱니다
wagurano Sep 2, 2026
8618e79
검색 결과 목록에서 KSCO 컬럼을 뺍니다
wagurano Sep 2, 2026
94b142c
KSCO 매핑이 있는 판정서를 삭제하면 FK 오류가 나던 버그를 고칩니다
wagurano Sep 2, 2026
fd268f6
GET / 에서 burden_body_part 토큰 집계 풀스캔을 한 번만 돌립니다
wagurano Sep 2, 2026
6a74c77
상세 검색(/search)에 메인 화면의 직업·부담 신체 부위·사망 여부·신청서 유형 필터를 추가합니다
wagurano Sep 2, 2026
946dffb
DiseaseCasesController 액션 이름을 라우트 관례에 맞춰 바꿉니다
wagurano Sep 2, 2026
8aa46bc
설계 문서를 실제 구현·리뷰 결과에 맞게 업데이트합니다
wagurano Sep 2, 2026
95bc63c
메인 화면에서 "상세 검색 (기존 화면)" 링크를 뺍니다
wagurano Sep 2, 2026
1af7f4f
상세 검색 결과 목록에 메인 화면의 직종명·담당 업무·신청서 종류·사망 여부 컬럼을 추가합니다
wagurano Sep 2, 2026
d63f131
MCP search_disease_cases에서 q 없이 구조화 필터만으로 검색할 수 있게 합니다
wagurano Sep 2, 2026
52f5839
웹 검색 폼에 고용형태·근무형태·업무관련성·KSCO 코드 필터를 노출합니다
wagurano Sep 2, 2026
f141d42
메인 화면(/)에서 고용형태·근무형태·업무관련성·KSCO 코드 필드를 뺍니다
wagurano Sep 2, 2026
25a939f
웹 검색 폼에서 KSCO 코드(숫자) 입력을 뺍니다
wagurano Sep 2, 2026
8eed86e
상세 검색에서 고용 형태 입력을 빼고, 결과 목록에 근무 형태·업무관련성 평가를 보여줍니다
wagurano Sep 2, 2026
a5186ac
메인·상세 검색 결과 목록에 "원문" 컬럼을 맨 앞에 보여줍니다
wagurano Sep 2, 2026
658e974
MCP search_disease_cases에서 q도 필터도 없는 완전히 빈 호출을 거부합니다
wagurano Sep 2, 2026
39fd865
루비 버전을 4.0.6에서 3.4.9로 내려 solid_mcp가 사전 컴파일 바이너리를 쓰게 합니다
wagurano Sep 2, 2026
7d4e035
README와 설계 문서를 최근 구현·리뷰 결과에 맞게 업데이트합니다
wagurano Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .ruby-version

This file was deleted.

1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 3.4.9
13 changes: 3 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=4.0.6
# Make sure RUBY_VERSION matches the Ruby version in .tool-versions
ARG RUBY_VERSION=3.4.9
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
Expand All @@ -32,16 +32,9 @@ FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config libssl-dev libclang-dev && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config libssl-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install Rust toolchain (solid_mcp's native extension is built from source; no
# precompiled binary gem supports this Ruby version yet)
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable

# Install application gems
COPY Gemfile Gemfile.lock vendor ./

Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ group :development do
# Use console on exceptions pages [ht
# tps://github.com/rails/web-console]
gem "web-console"

# Process manager for bin/dev (Procfile.dev) [https://github.com/DarthSim/overmind]
gem "overmind"
end

gem "actionmcp", "~> 0.107.1"
27 changes: 27 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ GEM
racc (~> 1.4)
openssl (4.0.1)
ostruct (0.6.3)
overmind (2.5.1)
overmind (2.5.1-aarch64-linux)
overmind (2.5.1-arm-linux)
overmind (2.5.1-arm64-darwin)
pagy (43.5.0)
json
uri
Expand Down Expand Up @@ -399,6 +403,21 @@ GEM
concurrent-ruby (~> 1.0)
railties (>= 8.0)
rb_sys (~> 0.9)
solid_mcp (0.5.0-arm64-darwin)
activejob (>= 8.0)
activerecord (>= 8.0)
concurrent-ruby (~> 1.0)
railties (>= 8.0)
solid_mcp (0.5.0-x86_64-linux)
activejob (>= 8.0)
activerecord (>= 8.0)
concurrent-ruby (~> 1.0)
railties (>= 8.0)
solid_mcp (0.5.0-x86_64-linux-musl)
activejob (>= 8.0)
activerecord (>= 8.0)
concurrent-ruby (~> 1.0)
railties (>= 8.0)
solid_queue (1.4.0)
activejob (>= 7.1)
activerecord (>= 7.1)
Expand Down Expand Up @@ -485,6 +504,7 @@ DEPENDENCIES
importmap-rails
jbuilder
kamal
overmind
pagy (~> 43.5)
propshaft
puma (>= 5.0)
Expand Down Expand Up @@ -593,6 +613,10 @@ CHECKSUMS
nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
overmind (2.5.1) sha256=df4676e787845173c3929029d2b2952055ffaf02fde0106054f98f4c2611ec9a
overmind (2.5.1-aarch64-linux) sha256=13a979e61278fb5a214600e844268460a86787a8f1a8ed962dcf6aa018f3f50e
overmind (2.5.1-arm-linux) sha256=5e12b5fb973dce118769648cf6549a48a0d424f947b40fbb30012f53308d23e1
overmind (2.5.1-arm64-darwin) sha256=8b1c9ea304ef524eaf335b4524d5dbd58e02ed9492397243f61cc49a8bd4eca7
pagy (43.5.0) sha256=58885d5f659e8db5b92cf35eeba674113e4e7bda12649b603c2d6908402570a4
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
Expand Down Expand Up @@ -636,6 +660,9 @@ CHECKSUMS
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
solid_cache (1.0.10) sha256=bc05a2fb3ac78a6f43cbb5946679cf9db67dd30d22939ededc385cb93e120d41
solid_mcp (0.5.0) sha256=6ccc57e9a12f8c2f5d6c62e7d29252cae1b63f326a128a97e47a48c420df3467
solid_mcp (0.5.0-arm64-darwin) sha256=f4000b98b5d08ec7674c5b9fa6167269ebf7477d02fc095dcf11890d166649dc
solid_mcp (0.5.0-x86_64-linux) sha256=b305c44a9bf3c1074e17b090919d2635752692501ef5200966e5c2be90cf03eb
solid_mcp (0.5.0-x86_64-linux-musl) sha256=65f54897047abd261e7126c390e0af0240972287b84c39e14afe058b88760a53
solid_queue (1.4.0) sha256=e6a18d196f0b27cb6e3c77c5b31258b05fb634f8ed64fb1866ed164047216c2a
sqlite3 (2.9.2-aarch64-linux-gnu) sha256=eeb86db55645b85327ba75129e3614658d974bf4da8fdc87018a0d42c59f6e42
sqlite3 (2.9.2-aarch64-linux-musl) sha256=4feff91fb8c2b13688da34b5627c9d1ed9cedb3ee87a7114ec82209147f07a6d
Expand Down
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

| 항목 | 버전 / 도구 |
|------|------------|
| Ruby | 4.0.6 |
| Ruby | 3.4.9 |
| Rails | 8.1 |
| 데이터베이스 | SQLite3 (FTS5 전문 검색) |
| MCP 서버 | actionmcp + Falcon |
Expand Down Expand Up @@ -50,13 +50,28 @@ FTS5 인덱스를 수동으로 재구성하려면:
rails fts:rebuild
```

### 직업·신체부위 기반 검색용 데이터 로드

메인 화면(`/`)의 직업·근무조건·신체부위·사망여부·신청서유형 필터와 KSCO 직업분류 연동을 위해, 위 임포트 이후 아래 두 rake task를 **순서대로** 실행합니다 (마이그레이션만으로는 데이터가 채워지지 않습니다).

```bash
rails "import:ksco_codes[wip/ksco-level-4-details.csv]"
rails "import:extracted_disease_cases[wip/extract_disease_cases_details_cerebras-ksco.csv]"
```

- `import:ksco_codes`: 한국표준직업분류(KSCO) 4단계 코드 체계를 `KscoCode`로 임포트합니다.
- `import:extracted_disease_cases`: LLM으로 추출한 직업·근무조건·신체부위·사망여부·신청서유형과 KSCO 매핑(`DiseaseCaseKscoCode`, 유사도 포함)을 기존 `DiseaseCase`에 채우고, `disease_cases_extracted_fts` 인덱스를 rebuild합니다.
- 자세한 설계와 데이터 모델은 [docs/workercare-search.plan.md](docs/workercare-search.plan.md)를 참고합니다.

## 주요 화면

| 경로 | 설명 |
|------|------|
| `GET /` | 메인 검색 (전문 검색) |
| `GET /search` | 상세 검색 (전문 검색 + 필터) |
| `GET /disease_cases/:id` | 판정서 상세 |
| `GET /` | 메인 검색 (직업·부담 신체 부위·사망여부·신청서유형 필터 + 전문 검색) — 간단한 검색 화면 |
| `GET /search` | 상세 검색 (메인 화면의 모든 필터 + 심의결과·질병분류·신체부위·판정일·근무 형태·업무관련성 평가 필터 + 전문 검색) |
| `GET /disease_cases/:case_no` | 판정서 상세 |

`/search`는 메인 화면(`/`)의 구조화 필터를 전부 포함해서 더 넓게 검색할 수 있습니다. 다만 고용 형태·KSCO 코드는 사용자가 직접 값을 골라 검색할 이유가 적어(고용 형태는 표현이 1,500개 넘게 잘게 쪼개져 있고, KSCO 코드는 숫자 코드) 두 화면 어디에도 입력 필드는 없습니다 — 쿼리스트링이나 MCP 클라이언트로는 계속 필터링할 수 있습니다.

## MCP 서버

Expand All @@ -71,7 +86,7 @@ https://..../mcp

| Tool | 설명 |
|------|------|
| `search_disease_cases` | 판정서 전문 검색 + 통계 집계 |
| `search_disease_cases` | 판정서 전문 검색(`q`, 선택) + 직업/사망여부/KSCO 코드 등 구조화 필터 + 통계 집계. `q`와 구조화 필터가 전부 비어있으면 에러를 반환합니다(전체 판정서를 검색 결과처럼 반환하지 않음) |
| `compare_approval_factors` | 인정/불인정 사례 패턴 비교 |
| `suggest_evidence` | 필요 증거 자료 제안 (룰 기반) |
| `get_procedure_guide` | 산재 신청 절차 안내 |
Expand Down Expand Up @@ -119,6 +134,7 @@ Access Token(비밀번호)이 비어 있으면 스크립트를 멈춥니다.

- [docs/workercare.plan.md](docs/workercare.plan.md) — 검색 서비스, FTS5, enum, 데이터 설계
- [docs/workercare-mcp.plan.md](docs/workercare-mcp.plan.md) — MCP 컴포넌트 설계, Tool/Prompt 명세
- [docs/workercare-search.plan.md](docs/workercare-search.plan.md) — 직업·신체부위·사망여부 기반 메인 검색 화면, KSCO 직업분류 연동 설계

## 라이선스

Expand Down
74 changes: 71 additions & 3 deletions app/controllers/disease_cases_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
class DiseaseCasesController < ApplicationController
MAX_SEARCH_RESULTS = 500
BURDEN_BODY_PART_CHECKBOX_LIMIT = 12
# wip/cerebras_prompts.rb 추출 스키마상 허용값 6개 전부 (docs/workercare-search.plan.md 3.2절) —
# DB에도 이 6개만 존재하는 진짜 enum이라 distinct pluck 대신 고정 목록을 순서대로 노출한다.
WORK_RELEVANCE_EVAL_OPTIONS = %w[매우_높음 높음 보통 낮음 매우_낮음 미흡].freeze

# / (메인 화면). 간단한 검색을 위해 직업·부담 신체 부위·사망 여부·신청서 유형만 노출한다 —
# 근무형태/업무관련성은 /search(상세 검색)에서만 노출한다. 고용형태·KSCO 코드는 사용자가
# 직접 골라 검색할 이유가 없어(고용형태는 값이 너무 잘게 쪼개져 있고, KSCO 코드는 숫자다)
# 웹 UI에는 아예 없다. apply_main_filters/main_search_params는 두 값 모두 그대로 받아서
# MCP 클라이언트(직업 설명 → 고용형태/KSCO 코드 매핑)는 계속 쓸 수 있다.
def index
perform_search
perform_search(legacy: false)
set_common_filter_options
end

# /search (상세 검색, 이전에는 root였다). 메인 화면 필터도 함께 쓸 수 있도록
# apply_main_filters를 추가로 적용한다.
def search
perform_search(legacy: true)
set_common_filter_options
set_advanced_filter_options
end

def show
Expand All @@ -11,14 +29,54 @@ def show

private

def perform_search
@scope, @fallback = DiseaseCase.search(search_params)
def perform_search(legacy:)
if legacy
@scope, @fallback = DiseaseCase.search(search_params)
@scope = DiseaseCase.apply_main_filters(@scope, main_search_params)
else
@scope, @fallback = DiseaseCase.main_search(main_search_params)
end
@pagy, @cases = paginate(@scope)
@metadata = build_metadata

log_search_event
end

def set_common_filter_options
counts = burden_body_part_token_counts
@burden_body_part_options = burden_body_part_options(counts)
@burden_body_part_datalist_options = burden_body_part_datalist_options(counts)
@application_type_options = application_type_options
end

def set_advanced_filter_options
@work_relevance_eval_options = WORK_RELEVANCE_EVAL_OPTIONS
end

# burden_body_part는 파이프(|) 구분 다중값 텍스트이며 실 데이터 기준 distinct 토큰이
# 1,000개를 넘는 자유 텍스트라(사전 정의된 enum이 아님 — 3.2절 참고), 체크박스로는
# 자주 쓰는 상위 N개만 노출하고 나머지는 <datalist> 자동완성 텍스트 입력으로 찾는다.
def burden_body_part_token_counts
counts = Hash.new(0)
DiseaseCase.where.not(burden_body_part: [ nil, "" ]).pluck(:burden_body_part).each do |raw|
raw.split("|").each { |token| counts[token.strip] += 1 unless token.strip.blank? }
end
counts
end

def burden_body_part_options(counts)
counts.sort_by { |_, count| -count }
.first(BURDEN_BODY_PART_CHECKBOX_LIMIT).map(&:first).sort
end

def burden_body_part_datalist_options(counts)
counts.keys.sort
end

def application_type_options
DiseaseCase.where.not(application_type: [ nil, "" ]).distinct.order(:application_type).pluck(:application_type)
end

def paginate(scope)
pagy(scope, items: 12, max_items: MAX_SEARCH_RESULTS)
end
Expand All @@ -37,6 +95,16 @@ def search_params
params.permit(:q, :result, :year, :decided_on_from, :decided_on_to, :sort, :commit, :search, search_in: [], disease_category: [], body_part: [])
end

def main_search_params
params.permit(
:q, :job_name, :job_description,
:death_status, :application_type, :employment_type, :work_type,
:work_relevance_eval, :sort, :commit, :search,
:burden_body_part_text,
burden_body_part: [], ksco_code: []
)
end

def log_search_event
Rails.logger.info({
event: "search",
Expand Down
Loading