Fitback AI는 Fitback 매장 관리 어시스턴트를 위한 FastAPI AI 계약 서버와 Neo4j 그래프 프로젝션 작업 공간입니다.
현재 이 저장소는 두 가지 기능을 제공합니다.
- Spring Boot 서버가 호출할 수 있는 FastAPI AI HTTP 계약 구현
- 실제 서비스 그래프 초기화, Neo4j AuraDB 적재, 그래프 카운트 검증
현재 FastAPI 응답은 OPENAI_API_KEY 또는 AI_API_KEY가 설정되어 있으면 OpenAI API를 호출하고, 키가 없으면 로컬 개발용 결정적 휴리스틱으로 생성됩니다.
- 실제 업무 데이터의 원천은 RDS 기반 메인 백엔드입니다.
- AuraDB는 AI 검색, 관계 탐색, 이벤트 타겟팅, 추천 근거 생성을 위한 그래프 프로젝션입니다.
.env에는 실제 AuraDB 인증 정보가 들어가므로 커밋하거나 출력하지 않습니다..env.example은 필요한 환경 변수 형식을 보여주는 안전한 참고 파일입니다.*상담메모_예시.xlsx같은 샘플 스프레드시트와.xlsx파일은 Git에서 제외합니다.- 처음에는 업무 데이터가 없어도 Neo4j schema와 온톨로지 노드만 먼저 초기화할 수 있습니다.
.
├── README.md
├── .env.example
├── requirements.txt
├── pyproject.toml
├── sql.example
├── docs/
│ └── fastapi요구사항.md
├── src/
│ └── fitback_ai/
│ ├── api.py
│ ├── api_models.py
│ ├── ai_provider.py
│ ├── ai_service.py
│ ├── cli.py
│ ├── config.py
│ ├── graph_persistence.py
│ ├── ontology.py
│ └── neo4j_loader.py
└── tests/
├── test_api.py
├── test_fastapi_contract.py
├── test_graph_persistence.py
├── test_neo4j_loader.py
└── test_ontology.py
주요 파일:
src/fitback_ai/api.py: FastAPI 앱과 라우트 선언src/fitback_ai/api_models.py: Pydantic 요청/응답 모델, camelCase alias, enum/date/UUID 검증src/fitback_ai/ai_service.py: FastAPI route에서 AI provider를 호출하는 facadesrc/fitback_ai/ai_provider.py: OpenAI API provider와 로컬 휴리스틱 providertests/test_api.py,tests/test_fastapi_contract.py: FastAPI 계약 테스트src/fitback_ai/graph_persistence.py: RDS 저장 완료 projection을 AuraDB graph projection으로 upsertsrc/fitback_ai/ontology.py: AI 판단과 그래프 적재가 공유하는 온톨로지 코드북src/fitback_ai/neo4j_loader.py: Neo4j schema 설정, ontology sync, graph upsert, count 검증src/fitback_ai/cli.py:init,load,verify명령sql.example: 목표 RDS 스타일 업무 schema 참고 자료docs/fastapi요구사항.md: Spring-to-FastAPI HTTP API 계약 문서
PowerShell:
python -m venv .venv
.\.venv\Scripts\python -m pip install -r requirements.txt현재 주요 의존성:
fastapi==0.139.0uvicorn==0.38.0neo4j==5.28.1openai==2.44.0pytest==8.4.1httpx==0.28.1
OpenAI 연동 환경 변수:
AI_PROVIDER=auto
OPENAI_API_KEY=your-openai-api-key
OPENAI_MODEL=gpt-4.1-mini
OPENAI_BASE_URL=https://api.openai.com/v1
AI_TIMEOUT_SECONDS=25AI_PROVIDER=auto에서는 OPENAI_API_KEY 또는 호환 alias인 AI_API_KEY가 있으면 OpenAI API를 사용하고, 키가 없으면 로컬 휴리스틱을 사용합니다. 운영에서는 OPENAI_API_KEY를 권장합니다.
로컬 AI 서버 실행:
.\.venv\Scripts\python -m uvicorn fitback_ai.api:app --host 0.0.0.0 --port 8000Spring 개발 환경에서는 AI_BASE_URL을 다음 주소로 설정합니다.
http://localhost:8000
API 문서:
- Swagger UI:
http://localhost:8000/docs - OpenAPI JSON:
http://localhost:8000/openapi.json
구현된 엔드포인트:
| 기능 | Method | Path |
|---|---|---|
| 문의 내용 중간 평가 | POST |
/ai/v1/inquiries/check-preview |
| 상담 내용 중간 평가 | POST |
/ai/v1/consultations/check-preview |
| 상담 AI 분석 | POST |
/ai/v1/consultations/analyze |
| RDS/AuraDB graph sync | POST |
/ai/v1/graph/consultations/sync |
| 다음 행동 추천 | POST |
/ai/v1/consultations/next-action |
| 고객 메시지 생성 | POST |
/ai/v1/messages/generate |
계약 동작:
- 요청/응답 JSON 필드명은 camelCase를 사용합니다.
- UUID, 날짜, offset 포함 일시, 문서화된 enum 값은 Pydantic/FastAPI가 검증합니다.
- 잘못된 요청 body는 FastAPI validation error와 함께
422를 반환합니다. - 처리 중
RuntimeError가 발생하면 다음 JSON 형식의500응답을 반환합니다.
{
"detail": "AI processing failed",
"code": "AI_PROCESSING_FAILED"
}- 성공 응답은 JSON object입니다.
- 필수 응답 문자열은
null, 빈 문자열, 공백 문자열로 반환하지 않습니다. - 빈 목록은
null대신[]로 반환합니다.
문의 내용 중간 평가:
Invoke-RestMethod -Method Post `
-Uri http://localhost:8000/ai/v1/inquiries/check-preview `
-ContentType "application/json" `
-Body '{
"rawText": "가격과 주 3회 PT 가능 여부를 문의했습니다.",
"serviceName": "퍼스널 트레이닝",
"inquiryStatus": "RECEIVED",
"customerInfo": {
"name": "홍길동",
"gender": "MALE",
"birthDate": "1995-04-12"
}
}'고객 메시지 생성:
Invoke-RestMethod -Method Post `
-Uri http://localhost:8000/ai/v1/messages/generate `
-ContentType "application/json" `
-Body '{
"customer": {
"customerId": "6c2d9b87-90aa-4ce2-96cb-a0875f103f04",
"name": "홍길동",
"preferredContactChannel": "KAKAO",
"status": "PENDING"
},
"latestConsultation": {
"consultationId": "6570aa21-d458-4885-9e69-d984fea51830",
"summary": "가격 부담으로 보류"
},
"aiInsight": {
"leadTemperature": "WARM",
"priorityScore": 75
},
"nonConversionReasons": [
{
"reasonType": "PRICE",
"role": "PRIMARY",
"reasonBasis": "가격 부담"
}
],
"nextBestAction": {
"title": "예산에 맞는 상품 안내",
"description": "예산별 상품을 제안합니다."
},
"event": null,
"messageOptions": {
"tonePreset": "FRIENDLY",
"versionType": "STANDARD"
}
}'사용자가 LLM에 직접 질문을 입력하지 않습니다. 화면의 버튼을 누르면 프론트/Spring이 현재 화면의 고객, 문의, 상담, 분석 데이터를 정해진 FastAPI 요청 body로 조립하고, FastAPI가 OpenAI provider를 호출해 계약된 JSON object를 반환합니다.
아래 AI 출력 값은 2026-07-18에 AI_PROVIDER=heuristic을 명시하고 FastAPI TestClient로 실제 endpoint를 호출해 받은 응답입니다.
사용자 액션:
문의 등록 화면에서 `문의 내용 검토` 버튼 클릭
처리 흐름:
프론트/Spring이 문의 메모, 서비스명, 문의 상태, 고객 기본 정보를 모아 /ai/v1/inquiries/check-preview로 POST합니다. 응답은 저장 전 AI가 확인한 7개 고정 항목과 확인 여부로 표시합니다.
FastAPI 입력:
{
"rawText": "가격과 주 3회 PT 가능 여부를 문의했습니다.",
"serviceName": "퍼스널 트레이닝",
"inquiryStatus": "RECEIVED",
"customerInfo": {
"name": "홍길동",
"gender": "MALE",
"birthDate": "1995-04-12"
}
}AI 출력:
{
"confirmedCount": 3,
"totalCount": 7,
"items": [
{
"key": "INTEREST_SERVICE",
"label": "관심 상품",
"confirmed": true,
"value": "퍼스널 트레이닝"
},
{
"key": "EXERCISE_GOAL",
"label": "운동 목적",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "EXERCISE_EXPERIENCE",
"label": "운동 경험",
"confirmed": true,
"value": "운동 경험이 언급됨"
},
{
"key": "INJURY_HISTORY",
"label": "부상 이력",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "CUSTOMER_REQUEST",
"label": "고객 요청",
"confirmed": true,
"value": "고객 요청이 언급됨"
},
{
"key": "COUNSELOR_RESPONSE",
"label": "나의 응대",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "SPECIAL_NOTE",
"label": "특이사항",
"confirmed": false,
"value": "아직 확인되지 않음"
}
]
}사용자 액션:
상담 등록 화면에서 `상담 메모 검토` 버튼 클릭
처리 흐름:
프론트/Spring이 상담 메모, 서비스명, 고객 기본 정보를 모아 /ai/v1/consultations/check-preview로 POST합니다. 응답은 상담 저장 전 AI가 확인한 7개 고정 항목과 확인 여부로 표시합니다.
FastAPI 입력:
{
"rawText": "체중 감량을 목표로 평일 저녁 운동 가능 여부를 상담했습니다.",
"serviceName": "퍼스널 트레이닝",
"customerInfo": {
"name": "홍길동",
"gender": "MALE",
"birthDate": "1995-04-12"
}
}AI 출력:
{
"confirmedCount": 3,
"totalCount": 7,
"items": [
{
"key": "INTEREST_SERVICE",
"label": "관심 상품",
"confirmed": true,
"value": "퍼스널 트레이닝"
},
{
"key": "EXERCISE_GOAL",
"label": "운동 목적",
"confirmed": true,
"value": "운동 목표가 언급됨"
},
{
"key": "EXERCISE_EXPERIENCE",
"label": "운동 경험",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "INJURY_HISTORY",
"label": "부상 이력",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "CUSTOMER_REQUEST",
"label": "고객 요청",
"confirmed": true,
"value": "고객 요청이 언급됨"
},
{
"key": "COUNSELOR_RESPONSE",
"label": "나의 응대",
"confirmed": false,
"value": "아직 확인되지 않음"
},
{
"key": "SPECIAL_NOTE",
"label": "특이사항",
"confirmed": false,
"value": "아직 확인되지 않음"
}
]
}사용자 액션:
상담 상세 화면에서 `AI 상담 분석` 버튼 클릭
처리 흐름:
프론트/Spring이 고객 정보, 상담 원문, 상담 상품, 매장 context, 첨부 상담 자료를 모아 /ai/v1/consultations/analyze로 POST합니다. 응답은 상담 요약, 고객 온도, 미전환 사유, 다음 행동, 후속 연락 정보로 저장하거나 화면에 표시합니다.
FastAPI 입력:
{
"customer": {
"customerId": "6c2d9b87-90aa-4ce2-96cb-a0875f103f04",
"name": "홍길동",
"gender": "MALE",
"birthDate": "1995-04-12",
"phoneNum": "010-1234-5678",
"preferredContactChannel": "KAKAO",
"status": "PENDING",
"inflowPathId": "71564cdc-6ad1-4978-a74e-150c576ce13c",
"inflowPathName": "인스타그램"
},
"consultation": {
"consultationId": "6570aa21-d458-4885-9e69-d984fea51830",
"sessionNo": 1,
"consultedAt": "2026-07-09T14:30:00+09:00",
"consultedServiceId": "88d53bb7-7210-4e77-a0c4-01778a54d68d",
"stage": "CONSULTATION",
"sourceType": "DIRECT",
"rawText": "체중 감량이 목표이고 가격을 고민 중입니다."
},
"service": {
"serviceId": "88d53bb7-7210-4e77-a0c4-01778a54d68d",
"serviceName": "퍼스널 트레이닝",
"description": "주 3회 1:1 트레이닝",
"price": 600000
},
"storeContext": {
"storeId": "28f43532-f2fc-4581-827e-880d06f3cd88",
"storeType": "GYM",
"registrationStatus": "PENDING"
},
"attachedMaterials": [
{
"materialType": "OTHER",
"title": "kakao-chat",
"content": "고객: PT 가격 문의드립니다.\n상담사: 현재 6개월권 이벤트가 있습니다."
}
]
}AI 출력:
{
"summary": "홍길동 고객은 퍼스널 트레이닝 상담에서 PRICE 관련 보류 신호를 보였습니다.",
"customerInsight": {
"leadTemperature": "WARM",
"temperatureBasis": "퍼스널 트레이닝 상담 내용과 PRICE 신호를 함께 고려했습니다.",
"priorityScore": 80
},
"nonConversionReasons": [
{
"reasonType": "PRICE",
"role": "PRIMARY",
"reasonBasis": "체중 감량이 목표이고 가격을 고민 중입니다.",
"confidence": "HIGH"
}
],
"nextBestAction": {
"title": "예산 맞춤 상품 안내",
"description": "퍼스널 트레이닝 선택지를 예산별로 정리해 부담을 낮춥니다."
},
"followUp": {
"recommendContactDate": "2026-07-12",
"memo": "KAKAO로 예산 맞춤 상품 안내 내용을 안내"
},
"followUpInsight": {
"persuasionPoint": {
"keyMessage": "퍼스널 트레이닝 선택지를 예산별로 정리해 부담을 낮춥니다."
},
"cautionNote": "PRICE 이슈를 압박하지 말고 선택지를 제안합니다.",
"actionBasis": {
"title": "예산 맞춤 상품 안내",
"description": "퍼스널 트레이닝 선택지를 예산별로 정리해 부담을 낮춥니다."
}
}
}사용자 액션:
고객 상세 또는 상담 상세 화면에서 `다음 행동 추천` 버튼 클릭
처리 흐름:
프론트/Spring이 고객 상태, 최근 상담 요약, 기존 AI 분석 결과를 모아 /ai/v1/consultations/next-action으로 POST합니다. 응답은 우선순위와 다음 행동, follow-up 일정 추천으로 사용합니다.
FastAPI 입력:
{
"customer": {
"customerId": "6c2d9b87-90aa-4ce2-96cb-a0875f103f04",
"status": "PENDING"
},
"latestConsultation": {
"consultationId": "6570aa21-d458-4885-9e69-d984fea51830",
"summary": "가격 부담으로 보류",
"rawText": "가격이 부담되어 고민 중입니다."
},
"aiAnalysis": {
"leadTemperature": "WARM",
"temperatureBasis": "운동 의사는 명확하지만 가격 고민이 있습니다.",
"nonConversionReasons": [
{
"reasonType": "PRICE",
"role": "PRIMARY",
"reasonBasis": "가격 부담"
}
]
}
}AI 출력:
{
"priorityScore": 80,
"nextBestAction": {
"title": "예산 맞춤 상품 안내",
"description": "상담 상품 선택지를 예산별로 정리해 부담을 낮춥니다."
},
"followUp": {
"recommendContactDate": "2026-07-26",
"memo": "예산 맞춤 상품 안내 후속 연락"
},
"followUpInsight": {
"persuasionPoint": {
"keyMessage": "상담 상품 선택지를 예산별로 정리해 부담을 낮춥니다."
},
"cautionNote": "PRICE 이슈를 압박하지 말고 선택지를 제안합니다.",
"actionBasis": {
"title": "예산 맞춤 상품 안내",
"description": "상담 상품 선택지를 예산별로 정리해 부담을 낮춥니다."
}
}
}사용자 액션:
후속 연락 화면에서 `고객 메시지 생성` 버튼 클릭
처리 흐름:
프론트/Spring이 고객 정보, 최근 상담 요약, AI insight, 미전환 사유, 다음 행동, 메시지 옵션을 모아 /ai/v1/messages/generate로 POST합니다. 응답은 담당자가 발송 전에 확인하고 수정할 수 있는 메시지 초안으로 표시합니다.
FastAPI 입력:
{
"customer": {
"customerId": "6c2d9b87-90aa-4ce2-96cb-a0875f103f04",
"name": "홍길동",
"preferredContactChannel": "KAKAO",
"status": "PENDING"
},
"latestConsultation": {
"consultationId": "6570aa21-d458-4885-9e69-d984fea51830",
"summary": "가격 부담으로 보류"
},
"aiInsight": {
"leadTemperature": "WARM",
"priorityScore": 75
},
"nonConversionReasons": [
{
"reasonType": "PRICE",
"role": "PRIMARY",
"reasonBasis": "가격 부담"
}
],
"nextBestAction": {
"title": "예산에 맞는 상품 안내",
"description": "예산별 상품을 제안합니다."
},
"event": null,
"messageOptions": {
"tonePreset": "FRIENDLY",
"versionType": "STANDARD",
"additionalInstruction": "첫 문장에 고객 이름을 넣어 주세요."
}
}AI 출력:
{
"content": "홍길동님, 상담 때 말씀해주신 부분을 바탕으로 예산에 맞는 상품 안내 안내를 드립니다. 예산별 상품을 제안합니다. PRICE 부담을 줄일 수 있게 선택지를 정리했습니다. 첫 문장에 고객 이름을 넣어 주세요.",
"versionType": "STANDARD",
"tonePreset": "FRIENDLY"
}전체 테스트:
.\.venv\Scripts\python -m pytest -q빠른 확인:
.\.venv\Scripts\python -m compileall -q src tests
.\.venv\Scripts\python -m pytest -q tests\test_api.pyAPI 테스트 범위:
/openapi.json에 5개 POST path가 존재하는지 확인/docs접근 가능 여부 확인- camelCase 요청/응답 처리 확인
- preview 응답이
confirmedCount,totalCount, 7개 고정items를 반환하는지 확인 - 분석 요청이
attachedMaterials필드를 필수로 요구하고, 빈 배열은 허용하는지 확인 - 정상 성공 응답 확인
- validation 실패 시
422확인 - 내부 처리 오류 시
code: AI_PROCESSING_FAILEDJSON 확인 - OpenAI provider가 Pydantic response format으로 구조화 응답을 요청하고 preview 코드북/count를 정규화하는지 fake client로 확인
API별 호출 테스트:
| API | 테스트 함수 | 확인 내용 |
|---|---|---|
GET /docs |
test_docs_endpoint_is_available |
Swagger UI 문서 endpoint가 200과 text/html로 응답하는지 확인 |
GET /openapi.json |
test_openapi_exposes_required_paths |
Spring에서 호출할 5개 POST path가 OpenAPI schema에 노출되는지 확인 |
POST /ai/v1/inquiries/check-preview |
test_inquiry_preview_accepts_camel_case_and_returns_json_object |
camelCase 요청을 받고 confirmedCount, totalCount, 7개 고정 items JSON을 반환하는지 확인 |
POST /ai/v1/consultations/check-preview |
test_consultation_preview_accepts_camel_case_and_returns_json_object |
상담 preview 요청이 7개 고정 preview item과 canonical label(나의 응대 포함)을 반환하는지 확인 |
POST /ai/v1/consultations/analyze |
test_consultation_analyze_returns_required_fields |
summary, customerInsight, nonConversionReasons, nextBestAction, followUp, followUpInsight 필수 필드를 반환하는지 확인 |
POST /ai/v1/consultations/analyze |
test_consultation_analyze_rejects_bad_datetime |
잘못된 datetime 입력을 422 validation error로 거절하는지 확인 |
POST /ai/v1/consultations/analyze |
test_analysis_requires_attached_materials_field_even_when_empty |
attachedMaterials 누락을 422로 거절하고 빈 배열은 허용하는지 확인 |
POST /ai/v1/consultations/next-action |
test_next_action_returns_required_contract |
priorityScore, nextBestAction, followUp 계약 필드를 반환하는지 확인 |
POST /ai/v1/messages/generate |
test_message_generate_returns_requested_enums |
요청한 tonePreset, versionType을 유지하고 메시지 본문을 반환하는지 확인 |
| 전체 POST API | test_all_endpoints_return_422_for_invalid_payloads |
각 API가 잘못된 payload를 422로 거절하는지 확인 |
| 전체 POST API | test_all_endpoints_return_processing_error_for_runtime_failures |
내부 AI 처리 예외가 500과 AI_PROCESSING_FAILED JSON으로 매핑되는지 확인 |
최근 검증 결과:
python -m compileall -q src tests
PASS
python -m pytest -q
39 passed, 1 warning
참고: 실제 OpenAI API 호출은 비용이 발생할 수 있어 자동 테스트에서 실행하지 않습니다. 운영 키 검증은 로컬에서 AI_PROVIDER=openai와 OPENAI_API_KEY를 설정한 뒤 FastAPI endpoint를 직접 호출해 확인합니다.
.env.example을 참고해 .env를 만들고 로컬 값으로 채웁니다.
NEO4J_URI=neo4j+s://example.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=replace-with-aura-password
NEO4J_DATABASE=neo4j
NEO4J_TRUST_SELF_SIGNED=false
GRAPH_PERSISTENCE_ENABLED=false
AURA_INSTANCEID=optional-instance-id
AURA_INSTANCENAME=optional-instance-name로컬 네트워크나 보안 제품이 TLS 인증서를 대체해 neo4j+s 연결이 실패할 때만 로컬 검증 용도로 NEO4J_TRUST_SELF_SIGNED=true를 사용합니다. 기본값은 false입니다.
RDS에 저장된 상담 분석 결과를 AuraDB에 동기화하려면 .env에서 다음 값을 켭니다.
GRAPH_PERSISTENCE_ENABLED=true이 값이 true일 때 /ai/v1/graph/consultations/sync는 BE/RDS가 저장한 실제 id를 기준으로 다음 projection을 AuraDB에 upsert합니다. /ai/v1/consultations/analyze는 AI 분석 응답만 반환하며 AuraDB에 직접 쓰지 않습니다.
Store,Service,Customer,ConsultationFollowUp,NonConversionReason,CustomerAiInsightReasonConcept,LeadTemperatureConcept와의 의미 관계
Neo4j 설정이 없거나 AuraDB 연결이 실패하면 sync API는 500과 AI_PROCESSING_FAILED로 실패합니다. 이 값이 false이거나 없으면 sync API는 persisted=false를 반환하고 AuraDB에는 쓰지 않습니다.
업무 데이터가 아직 없을 때 schema와 온톨로지만 초기화:
.\.venv\Scripts\python -m fitback_ai initSpring/RDS에서 추출한 JSON payload를 Neo4j/AuraDB에 적재:
.\.venv\Scripts\python -m fitback_ai load --input .omx\production-graph.json입력 파일 없이 load를 실행하면 init과 동일하게 빈 그래프를 준비합니다.
.\.venv\Scripts\python -m fitback_ai load현재 graph count 검증:
.\.venv\Scripts\python -m fitback_ai verify명령 출력 항목:
elapsedMsrecordCount(init,load)- label별 count
초기 빈 데이터 기준 count 예시:
{
"Store": 0,
"Customer": 0,
"Consultation": 0,
"ReasonConcept": 6,
"ActionConcept": 6,
"LeadTemperatureConcept": 3
}production projection은 다음 Neo4j label을 생성합니다.
StoreUserServiceCustomerConsultationFollowUpNonConversionReasonConsultationSignalCustomerAiInsightEventEventTargetMessageTemplateContactResultOntologyConceptReasonConceptReasonCategoryActionConceptLeadTemperatureConcept
주요 관계 패턴:
(:Store)-[:HAS_CUSTOMER]->(:Customer)
(:Store)-[:OFFERS]->(:Service)
(:Store)-[:RUNS_EVENT]->(:Event)
(:Customer)-[:INTERESTED_IN]->(:Service)
(:Customer)-[:HAD_CONSULTATION]->(:Consultation)
(:Consultation)-[:HAS_FOLLOW_UP]->(:FollowUp)
(:Consultation)-[:HAS_NON_CONVERSION_REASON]->(:NonConversionReason)
(:Consultation)-[:HAS_SIGNAL]->(:ConsultationSignal)
(:Customer)-[:HAS_AI_INSIGHT]->(:CustomerAiInsight)
(:Event)-[:TARGETS]->(:EventTarget)
(:EventTarget)-[:TARGET_CUSTOMER]->(:Customer)
(:Customer)-[:HAS_MESSAGE]->(:MessageTemplate)
(:FollowUp)-[:GENERATED_MESSAGE]->(:MessageTemplate)
(:Customer)-[:HAS_CONTACT_RESULT]->(:ContactResult)이 구조는 다음 관리자 질문을 지원하기 위한 기반입니다.
- 오늘 후속 연락이 필요한 고객은 누구인가?
- 이 고객의 우선순위가 높은 이유는 무엇인가?
- 특정 이벤트 캠페인을 보낼 고객은 누구인가?
- 주요 미전환 사유는 무엇인가?
- 고객 상담 이력에 맞는 메시지 초안은 무엇인가?
적재된 고객과 연결 그래프 확인:
MATCH p = (c:Customer)-[*1..3]-(n)
RETURN p
LIMIT 50label별 count 확인:
MATCH (n)
RETURN labels(n) AS labels, count(*) AS count
ORDER BY labels이 폴더를 읽는 AI agent는 다음 규칙을 지킵니다.
.env값을 읽거나 출력하거나 커밋하거나 요약하지 않습니다.- 설정 형식 확인에는
.env.example을 사용합니다. - 스프레드시트와 무시된 로컬 샘플은 untracked 상태로 둡니다.
- HTTP 계약 형태 변경은 우선
api_models.py를 수정합니다. - AI provider 동작 변경은 우선
ai_provider.py, route 연결 변경은ai_service.py를 수정합니다. - Ontology 변경은
ontology.py, graph write 변경은neo4j_loader.py를 우선 수정합니다. - 처음 데이터가 없는 운영 환경을 지원합니다.
init과 빈load는 schema와 ontology만 준비해야 합니다. - tenant boundary를 보존합니다. 실제 business node에는
storeId를 유지합니다. - 코드 변경 커밋 전
python -m pytest -q를 실행합니다. - AuraDB 인증 정보가 있을 때
python -m fitback_ai init과python -m fitback_ai verify를 실행합니다.
- OpenAI 연동은 구현되어 있지만 실제 운영 품질은 Spring 개발 환경에서 추가 통합 테스트가 필요합니다.
AI_PROVIDER=heuristic또는 API key가 없는auto모드는 실제 LLM 출력이 아니라 결정적 휴리스틱입니다.- embedding/vector index는 아직 생성하지 않습니다.
- Spring 개발 환경에서
AI_BASE_URL로 실제 통합 테스트를 추가로 수행해야 합니다. - Aura Agent/Bloom이 graph를 올바르게 조회하려면 별도의 Cypher tool이나 prompt가 필요할 수 있습니다.
- RDS가 원천 데이터 저장소이며, AuraDB는 AI projection layer입니다.