cfd550bed8
Deploy via SSH / remote-deploy (push) Failing after 6s
Backend (server/src): - services/anthropic.ts — Claude API 래퍼 (키 없으면 룰베이스 fallback) - services/ocr.ts — Naver Clova + Google Vision 듀얼 연동 + 영수증 필드 파서 - services/solapi.ts — 카카오 알림톡 HMAC 서명 + 드라이런 - services/expoPush.ts — Expo Push API 전송 - services/codef.ts — 보험 통합조회 mock + 실연동 포인트 - routes/ai.ts, ocr.ts, devices.ts, social.ts (naver/apple), alimtalk.ts, codef.ts - Prisma: PushDevice 모델 + binaryTargets linux-musl-openssl-3.0.x - Dockerfile: apk add openssl (Prisma schema engine 정상화) - api-secrets에 9개 외부 API 키 슬롯 추가 (optional) Frontend: - api/endpoints.ts: aiApi, ocrApi, deviceApi, socialApi, codefApi - services/kakao.ts — Kakao JS SDK 동적 로드 + Auth.login - services/push.ts — expo-notifications 권한/토큰 등록 + 서버 전송 - LoginScreen — 카카오/네이버/애플 버튼 (웹은 토큰 입력 fallback) - AIJudgeScreen — 실제 /ai/claim-judge 호출, source(llm/rules) 표시 - ClaimScreen — 영수증 촬영 시 자동 OCR → 병원/날짜/제목 자동 기입 - useAuthStore hydrate 시 푸시 토큰 등록 Infra: - eas.json (development/preview/production 빌드 프로필) - API_KEYS.md — 9개 외부 서비스 발급/등록 가이드 - scripts/deploy-remote.sh 개선 (sudo 정확히, traefik cp 버그 수정, API fail 시 로그 출력) - deploy/k8s/api.yaml — 외부 API 키 환경변수 매핑 (optional=true) CI/CD: - .gitea/workflows/deploy.yml → SSH 기반으로 전환 (appleboy/ssh-action으로 서버 접속 → deploy-remote.sh 실행) - 필요 Secrets: SSH_HOST, SSH_USER, SSH_PASSWORD Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
// Thin Anthropic SDK wrapper — 키가 없으면 룰베이스 fallback
|
|
// 실제 LLM 호출은 ANTHROPIC_API_KEY 환경변수가 있을 때만
|
|
|
|
const API_BASE = 'https://api.anthropic.com/v1/messages';
|
|
const MODEL = process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-4-5';
|
|
|
|
export type JudgeResult = {
|
|
available: boolean;
|
|
policies: Array<{ name: string; desc: string }>;
|
|
docs: string[];
|
|
estimated: string;
|
|
caution?: string;
|
|
source: 'llm' | 'rules';
|
|
raw?: string;
|
|
};
|
|
|
|
export async function judgeClaimByAI(input: string, context: { policies: Array<{ type: string; name: string; coverage: number }> }): Promise<JudgeResult> {
|
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
if (!apiKey) {
|
|
return fallback(input);
|
|
}
|
|
|
|
const systemPrompt = `너는 한국 보험금 청구 전문 상담사야. 사용자가 자신의 증상/시술/치료 내용을 말하면,
|
|
사용자가 가입한 보험 기반으로 청구 가능 여부, 해당 보험 상품, 필요 서류, 예상 수령액, 주의사항을
|
|
JSON 형식으로 답해. 추측이지만 근거가 있게 설명.
|
|
|
|
사용자의 가입 보험 목록:
|
|
${JSON.stringify(context.policies, null, 2)}
|
|
|
|
응답은 반드시 아래 JSON 스키마로만:
|
|
{
|
|
"available": boolean,
|
|
"policies": [{"name": string, "desc": string}],
|
|
"docs": [string],
|
|
"estimated": string,
|
|
"caution": string (선택)
|
|
}`;
|
|
|
|
const body = {
|
|
model: MODEL,
|
|
max_tokens: 1024,
|
|
system: systemPrompt,
|
|
messages: [{ role: 'user', content: input }],
|
|
};
|
|
|
|
try {
|
|
const res = await fetch(API_BASE, {
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
'x-api-key': apiKey,
|
|
'anthropic-version': '2023-06-01',
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
if (!res.ok) throw new Error(`Anthropic ${res.status}`);
|
|
const data = (await res.json()) as any;
|
|
const text = data?.content?.[0]?.text ?? '';
|
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
if (!jsonMatch) throw new Error('LLM JSON not found');
|
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
return {
|
|
available: !!parsed.available,
|
|
policies: parsed.policies ?? [],
|
|
docs: parsed.docs ?? [],
|
|
estimated: parsed.estimated ?? '-',
|
|
caution: parsed.caution,
|
|
source: 'llm',
|
|
raw: text,
|
|
};
|
|
} catch (e) {
|
|
return fallback(input);
|
|
}
|
|
}
|
|
|
|
function fallback(input: string): JudgeResult {
|
|
const q = input.toLowerCase();
|
|
if (q.includes('발목') || q.includes('삐') || q.includes('넘어')) {
|
|
return {
|
|
available: true,
|
|
policies: [
|
|
{ name: '실손의료비', desc: '정형외과 진료비 청구 가능' },
|
|
{ name: '상해보험 통원일당', desc: '1일 1~5만원 (가입 금액 따라)' },
|
|
],
|
|
docs: ['정형외과 영수증', '진단서 (S93 발목 염좌)'],
|
|
estimated: '5~15만원',
|
|
source: 'rules',
|
|
};
|
|
}
|
|
if (q.includes('감기')) {
|
|
return {
|
|
available: true,
|
|
policies: [{ name: '실손 통원의료비', desc: '1회 1만원 공제 후 80% 보장' }],
|
|
docs: ['병원 영수증', '처방전'],
|
|
estimated: '2~4만원',
|
|
caution: '실손 외래 통원 건당 자기부담금 공제',
|
|
source: 'rules',
|
|
};
|
|
}
|
|
if (q.includes('용종') || q.includes('내시경')) {
|
|
return {
|
|
available: true,
|
|
policies: [
|
|
{ name: '실손의료비', desc: '내시경/제거 시술비 청구' },
|
|
{ name: '수술비 특약', desc: '1종 수술 해당 - 10~50만원' },
|
|
],
|
|
docs: ['수술확인서', '세부내역서', '조직검사 결과지'],
|
|
estimated: '15~50만원',
|
|
source: 'rules',
|
|
};
|
|
}
|
|
if (q.includes('도수치료') || q.includes('물리치료')) {
|
|
return {
|
|
available: true,
|
|
policies: [{ name: '실손 비급여 특약', desc: '도수치료 1회 25만원 한도' }],
|
|
docs: ['병원 영수증 (세부내역서 포함)', '의사 소견서'],
|
|
estimated: '회당 3~25만원',
|
|
source: 'rules',
|
|
};
|
|
}
|
|
return {
|
|
available: false,
|
|
policies: [],
|
|
docs: [],
|
|
estimated: '-',
|
|
caution: '더 구체적인 증상/시술명을 알려주시면 정확히 판정해 드릴 수 있어요.',
|
|
source: 'rules',
|
|
};
|
|
}
|