Skip to content
Open

ss #34

Show file tree
Hide file tree
Changes from all commits
Commits
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
490 changes: 295 additions & 195 deletions README.md

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions ai-api.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[Unit]
Description=AI API Gunicorn Service
After=network.target

[Service]
Type=notify
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/AI
Environment="PATH=/home/ubuntu/AI/venv/bin"
ExecStart=/home/ubuntu/AI/venv/bin/gunicorn -c gunicorn.conf.py run:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target






67 changes: 62 additions & 5 deletions app/DTO/AiRepotDto.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pydantic import BaseModel,ConfigDict, Field, field_validator
from datetime import datetime
from typing import Optional, Any
import json

class AiReportListResponse(BaseModel):
id: int
Expand All @@ -16,11 +17,11 @@ class AiReportResponse(BaseModel):
userName:Optional[str]=None
created_at:datetime
CstID:int
HmtID:Optional[int]=None
testReport:Optional[str]=None
scoreReport:Optional[str]=None
majorReport:Optional[str]=None
totalReport:Optional[str]=None
HmtID:int
testReport:Optional[dict]=None
scoreReport:Optional[dict]=None
majorReport:Optional[dict]=None
totalReport:Optional[dict]=None

model_config = ConfigDict(from_attributes=True)

Expand All @@ -32,6 +33,62 @@ def get_user_name(cls, v, info):
return getattr(info.data.user, 'name', None)
return v

@field_validator('testReport', mode='before')
@classmethod
def parse_test_report(cls, v, info):
"""testReport 문자열을 JSON으로 파싱합니다."""
if isinstance(v, str):
# 'None' 문자열이나 빈 문자열은 None으로 처리
if v.lower() in ['none', 'null', ''] or v.strip() == '':
return None
try:
return json.loads(v)
except json.JSONDecodeError:
return v
return v

@field_validator('scoreReport', mode='before')
@classmethod
def parse_score_report(cls, v, info):
"""scoreReport 문자열을 JSON으로 파싱합니다."""
if isinstance(v, str):
# 'None' 문자열이나 빈 문자열은 None으로 처리
if v.lower() in ['none', 'null', ''] or v.strip() == '':
return None
try:
return json.loads(v)
except json.JSONDecodeError:
return v
return v

@field_validator('majorReport', mode='before')
@classmethod
def parse_major_report(cls, v, info):
"""majorReport 문자열을 JSON으로 파싱합니다."""
if isinstance(v, str):
# 'None' 문자열이나 빈 문자열은 None으로 처리
if v.lower() in ['none', 'null', ''] or v.strip() == '':
return None
try:
return json.loads(v)
except json.JSONDecodeError:
return v
return v

@field_validator('totalReport', mode='before')
@classmethod
def parse_total_report(cls, v, info):
"""totalReport 문자열을 JSON으로 파싱합니다."""
if isinstance(v, str):
# 'None' 문자열이나 빈 문자열은 None으로 처리
if v.lower() in ['none', 'null', ''] or v.strip() == '':
return None
try:
return json.loads(v)
except json.JSONDecodeError:
return v
return v

class AiReportRequest(BaseModel):
reportGradeNum: int=Field(description='레포트 생성할 학년(반영 성적 및 추천 내용 달라짐)')
reportTermNum: int=Field(description='레포트 생성할 학기(반영 성적 및 추천 내용 달라짐)')
2 changes: 1 addition & 1 deletion app/domain/MajorBookmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sqlalchemy import Column, Integer, String, ForeignKey, BigInteger

class MajorBookmark(BaseEntity):
__tablename__ = "mahor_bookmark"
__tablename__ = "major_bookmark"
__table_args__ = {
"mysql_engine": "InnoDB",
"mysql_charset": "utf8mb4",
Expand Down
2 changes: 1 addition & 1 deletion app/domain/reportModule/Report.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ class Report(BaseEntity):
userGrade=Column(Integer,name='user_grade',nullable=False)#유저 학년

user=relationship("User",back_populates="reports")
reportScores=relationship("ReportScore",back_populates="report")
reportScores=relationship("ReportScore", back_populates="report")
2 changes: 1 addition & 1 deletion app/domain/reportModule/ReportScore.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ReportScore(BaseEntity):
score=Column(Integer,name='score',nullable=True) #원점수
credit=Column(Integer,name='credit',nullable=False) #학점

rId=Column(BigInteger,ForeignKey('report.r_id'),name='r_id',nullable=False)
rId=Column(BigInteger, ForeignKey('report.r_id'), name='r_id', nullable=False)

report=relationship("Report", back_populates="reportScores")

Expand Down
2 changes: 1 addition & 1 deletion app/gptApi/testReport.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def output_constructor() -> str:
return """
다음과 같은 json 형태로 출력해줘
{
cst_hmt_test_report: 3000자 내외 택스트 형태로 레포트 결과물
content: 3000자 내외 택스트 형태로 레포트 결과물
major: 추천 학과 리스트
field: 추천 계열 리스트
hmt: 흥미 유형 상위 2개 영역 리스트 (예시: ["1순위 R유형","2순위 S유형"])
Expand Down
2 changes: 1 addition & 1 deletion app/gptApi/totalReport.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def output_constructor() ->str:
prompt = """
다음과 같은 json으로 출력해줘
{
content: 출력결과
content: 출력결과 4000자내외
}
"""
return prompt
Expand Down
26 changes: 26 additions & 0 deletions app/repository/majorBookmarkRepository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from domain.MajorBookmark import MajorBookmark
from .Repository import BaseRepository
from typing import Optional, List
from sqlalchemy.orm import joinedload

class MajorBookmarkRepository(BaseRepository[MajorBookmark]):
def __init__(self):
super().__init__(model=MajorBookmark)

def getByUserId(self, uid: str) -> List[MajorBookmark]:
"""사용자 ID로 북마크된 학과-학교 목록을 조회합니다."""
return (self.session.query(self.model)
.options(joinedload(self.model.major), joinedload(self.model.university))
.filter(self.model.uid == uid)
.all())

def getByUserIdWithUniversityAndMajor(self, uid: str) -> List[MajorBookmark]:
"""사용자 ID로 대학교와 학과 정보가 모두 있는 북마크만 조회합니다."""
return (self.session.query(self.model)
.options(joinedload(self.model.major), joinedload(self.model.university))
.filter(self.model.uid == uid)
.filter(self.model.univId.isnot(None))
.filter(self.model.major_id.isnot(None))
.all())

majorBookmarkRepository = MajorBookmarkRepository()
116 changes: 116 additions & 0 deletions app/routes/AdmissionPossibilityController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from fastapi import APIRouter, Depends
from typing import Dict
from services.AdmissionPossibilityService import admissionPossibilityService
from login.oauth_jwt_auth import get_current_user
from domain.User import User
from globals import create_success_response, ErrorCode, raise_business_exception

router = APIRouter(prefix="/api/admission-possibility", tags=["합격가능성"])

@router.get("/my", summary="내 합격가능성 조회")
async def get_my_admission_possibility(
current_user: User = Depends(get_current_user)
) -> Dict:
"""
현재 로그인된 사용자의 북마크된 학과-학교에 대한 합격가능성을 분석합니다.

Returns:
Dict: 합격가능성 분석 결과
"""
try:
result = admissionPossibilityService.getUserAdmissionPossibility(current_user.uid)

if "error" in result:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
result["error"]
)

return create_success_response(
result,
"합격가능성 분석이 완료되었습니다."
)

except Exception as e:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
f"합격가능성 분석 중 오류가 발생했습니다: {str(e)}"
)

@router.get("/bookmark/{bookmark_id}", summary="특정 북마크 합격가능성 조회")
async def get_bookmark_possibility(
bookmark_id: int,
current_user: User = Depends(get_current_user)
) -> Dict:
"""
특정 북마크에 대한 합격가능성을 분석합니다.

Args:
bookmark_id: 북마크 ID

Returns:
Dict: 특정 북마크의 합격가능성 분석 결과
"""
try:
result = admissionPossibilityService.getSpecificBookmarkPossibility(current_user.uid, bookmark_id)

if "error" in result:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
result["error"]
)

return create_success_response(
result,
"북마크 합격가능성 분석이 완료되었습니다."
)

except Exception as e:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
f"북마크 분석 중 오류가 발생했습니다: {str(e)}"
)

@router.get("/summary", summary="내 합격가능성 요약 조회")
async def get_admission_possibility_summary(
current_user: User = Depends(get_current_user)
) -> Dict:
"""
현재 로그인된 사용자의 합격가능성 요약 정보를 제공합니다.

Returns:
Dict: 합격가능성 요약 정보
"""
try:
result = admissionPossibilityService.getUserAdmissionPossibility(current_user.uid)

if "error" in result:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
result["error"]
)

# 요약 정보만 추출
if "analysis" in result and result["analysis"]:
summary = result["analysis"].get("summary", {})
return create_success_response(
{
"uid": current_user.uid,
"summary": summary
},
"합격가능성 요약 조회가 완료되었습니다."
)
else:
return create_success_response(
{
"uid": current_user.uid,
"summary": {"message": "분석할 북마크가 없습니다."}
},
"북마크가 없습니다."
)

except Exception as e:
raise_business_exception(
ErrorCode.UNKNOWN_ERROR,
f"합격가능성 요약 조회 중 오류가 발생했습니다: {str(e)}"
)
2 changes: 1 addition & 1 deletion app/routes/AiReportController.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

router =APIRouter(prefix='/aireport')

@router.post("/me",summary="현재 유저의 모든 aiReport를 가져옵니다.")
@router.get("/me",summary="현재 유저의 모든 aiReport를 가져옵니다.")
async def getAiReportsByMe(current_user:User = Depends(get_current_user)):
try:
result_list:List[AiReportListResponse]= aiReportService.getAllAiReportsByUser(current_user.uid)
Expand Down
5 changes: 4 additions & 1 deletion app/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from routes.CstController import router as cst_router
from routes.AuthController import router as auth_router
from routes.AiReportController import router as ai_report_router
from routes.AdmissionPossibilityController import router as admission_possibility_router
from globals import setup_exception_handlers
from util.globalDB.db_context import set_db, reset_db
from db import SessionLocal,engine
Expand Down Expand Up @@ -74,6 +75,7 @@ async def db_session_middleware(request: Request, call_next):
app.include_router(cst_router, tags=["직업적성검사"])

app.include_router(ai_report_router,tags=["ai레포트"])
app.include_router(admission_possibility_router, tags=["합격가능성"])

@app.get("/")
async def root():
Expand All @@ -92,11 +94,12 @@ async def test_error():
raise_file_exception(ErrorCode.PDF_PROCESSING_ERROR, "테스트 에러 메시지입니다.")

if __name__ == "__main__":

uvicorn.run(
"run:app", # app 폴더 안에서 실행될 때의 경로
host="0.0.0.0",
port=8081,
reload=True,
ssl_keyfile="../127.0.0.1+1-key.pem", # 개인키 파일 경로
ssl_certfile="../127.0.0.1.pem", # 인증서 파일 경로
log_level="debug" # 더 자세한 로그
)
Loading
Loading