From c88ff358761615bfe15ca08824b880910642e807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=A7=80=EB=AF=BC?= <108014449+stopmin@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:53:38 +0900 Subject: [PATCH 1/3] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c884e93..161822d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ - **implicit**: 협업 필터링 기반의 추천 알고리즘 - **사용자 선호도 분석**: 사용자 행동 데이터를 기반으로 맞춤형 추천 제공 -- [추천 시스템](https://bubble-pick-143.notion.site/89314631a2404d76811c6ab6f550b43c?pvs=4) --- From 7efe2a713037595dead3a3ac599edaca1c741f8e Mon Sep 17 00:00:00 2001 From: Vackam <53655740+Vackam@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:55:59 +0900 Subject: [PATCH 2/3] =?UTF-8?q?router=20renturn=20=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 기사 재생성 LLM 요청 프로토타입 #25 (#36) * 환경변수 파일 ignore 추가 * python-dotenv 의존성 추가 - .env 파일 * get_platform_client 생성 * 네이밍 수정 * 쉬운 기사 변환 프로토타입 생성 * 프롬프트 버저닝 및 txt파일로 분리 * ai 요청 결과를 json으로 파싱 * 링크를 넣으면 크롤링 해서 새로운 쉬운 기사로 생성하여 DB에 저장 * prompt 수정 * return 값 수정 --------- Co-authored-by: 정지민 <108014449+stopmin@users.noreply.github.com> Co-authored-by: Vackam --- .../prompt/graphic_article/2024-07-10.txt | 3 + app/service/api_visualization_service.py | 60 +++++++++++++------ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/app/model/prompt/graphic_article/2024-07-10.txt b/app/model/prompt/graphic_article/2024-07-10.txt index 22598e0..dc9fdd6 100644 --- a/app/model/prompt/graphic_article/2024-07-10.txt +++ b/app/model/prompt/graphic_article/2024-07-10.txt @@ -19,3 +19,6 @@ Response format: "body": 매일의 판매액을 한눈에 볼 수 있게 했어요 📈\n매출 데이터는 날짜별로 그룹화했어요 🗓️\n날짜 순서대로 정렬했어요 📅\n그래프에서 각 날짜의 판매액을 확인할 수 있어요 🛒\n이제 일별 매출 트렌드를 알 수 있어요 😊 } } +경고: groupby에서 preprocess_groupby 메서드를 사용할 때는 다음과 같이 받아와야 합니다. +async def preprocess_groupby(self, df, by, agg_func): + return df.groupby(by).agg(agg_func).reset_index() diff --git a/app/service/api_visualization_service.py b/app/service/api_visualization_service.py index 2887a6a..7a4019c 100644 --- a/app/service/api_visualization_service.py +++ b/app/service/api_visualization_service.py @@ -2,12 +2,12 @@ # pylint: disable=R0911 # pylint: disable=C0206 # pylint: disable=C0327 +import json from enum import Enum from typing import List import pandas as pd import plotly.express as px -import plotly.io as pio from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession @@ -316,31 +316,53 @@ async def create_article( if not await graph_service.get_api_data(data): raise HTTPException(status_code=400, detail="Couldn't get data") + # 여기가 ai보고 데이터 받아오라고 하는 곳. ai_result = await graph_service.graph_info(title=title) - - fig = await graph_service.plot_graph( - ai_result["graph_type"], - ai_result["x_value"], - ai_result["y_value"], - ai_result["preprocessing_steps"], - **ai_result["kwargs"], + # 전처리 완료 + await graph_service.apply_preprocessing( + graph_service.dataset, ai_result["preprocessing_steps"] ) - # html 다 만든 것 - html_str = pio.to_html(fig, full_html=True) - - # 둘 다 html_str은 뱉는 걸로. - # return이 두 개입니다. + # 그래프 그리는 부분 + # 근데 이 곳을 제끼고 return 을 달리하라는 거임. + + # fig = await graph_service.plot_graph( + # ai_result["graph_type"], + # ai_result["x_value"], + # ai_result["y_value"], + # ai_result["preprocessing_steps"], + # **ai_result["kwargs"], + # ) + # + # # html 다 만든 것 + # html_str = pio.to_html(fig, full_html=True) + + x = graph_service.dataset[ai_result["x_value"]].tolist() + y = graph_service.dataset[ai_result["y_value"]].tolist() + graph_type = ai_result["graph_type"] + # 우선 무슨 값을 넣을 지 몰라서 hard coding 합니다. + mode = "lines+markers" + marker = "{color: 'red'}" + + data = [{"x": x, "y": y, "type": graph_type, "mode": mode, "marker": marker}] + + layout = {"title": title} + + chart_config = { + "data": data, + "layout": layout, + } + + json_data = json.dumps(chart_config, ensure_ascii=False) if user_input: - return html_str, "" + # 유저의 통제시 그냥 html만 리턴! + return json_data, "" # 기사를 만드는 경우는 저장을 한다! - repository = ApiVisualizationService() - content = ai_result["article"]["body"] await repository.create_article( title=title, - graph_html=html_str, - content=content, + graph_html=json.dumps(chart_config), + content=ai_result["article"]["body"], session=session, ) - return html_str, content + return json_data, ai_result["article"]["body"] From 68f798fb2a8c20f2e74ace8770ea883c21a1b0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=A7=80=EB=AF=BC?= <108014449+stopmin@users.noreply.github.com> Date: Sat, 20 Jul 2024 00:50:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?usertype=20=EA=B4=80=EB=A0=A8=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/router/user_type_router.py | 92 +++++++++++------------- app/service/user_type_service.py | 116 +++++++++++++++++++------------ 2 files changed, 109 insertions(+), 99 deletions(-) diff --git a/app/router/user_type_router.py b/app/router/user_type_router.py index a7e55a7..8dcafaf 100644 --- a/app/router/user_type_router.py +++ b/app/router/user_type_router.py @@ -1,20 +1,18 @@ -from typing import List, Dict +from typing import Dict, List from fastapi import APIRouter, Depends from groq import BaseModel from sqlalchemy.ext.asyncio import AsyncSession -from app.database.repository import model_to_dict from app.database.session import get_db_session -from app.model.user_type import UserType -from app.recommend.recommend_service import RecommendService, user_id_to_classification_id, \ - user_type_to_classification_id +from app.recommend.recommend_service import user_type_to_classification_id from app.repository.crawled_article_crud import CrawledArticleRepository from app.repository.recommend_crud import RecommendRepository from app.service.user_type_service import ( UserTypeQuestionnaire, UserTypeQuestionnaireRequestDTO, - UserTypeService, calculate_user_type, + UserTypeService, + calculate_user_type, ) from app.utils.generic_response import GenericResponseDTO @@ -27,73 +25,61 @@ class ArticleResponseDTO(BaseModel): content: str pubDate: str image: str + + class UserTypeResponseDTO(BaseModel): userType: Dict recommendNews: List[ArticleResponseDTO] - @user_type_router.get( - "/user-type/questionnaire", + "/usertype/form", response_model=GenericResponseDTO[List[UserTypeQuestionnaire]], ) async def get_questionnaire(): data = await UserTypeService().get_questionnaire() return GenericResponseDTO[List[UserTypeQuestionnaire]]( - data=data, message="Questionnaire retrieved successfully", result=True + data=data, message="유형검사 질문지 조회에 성공", result=True ) -@user_type_router.post("/user-type", response_model=GenericResponseDTO[UserTypeResponseDTO]) +@user_type_router.post( + "/usertype/calculate", response_model=GenericResponseDTO[UserTypeResponseDTO] +) async def create_user_type_by_answers( request: UserTypeQuestionnaireRequestDTO, session: AsyncSession = Depends(get_db_session), ): - recommendNew_ids = [] - if request.id == None: - userType = calculate_user_type( - request, UserTypeService().get_questionnaire_data() - ) - recommendNew_ids = await RecommendRepository().get( - pk=await user_type_to_classification_id(userType), - session=session) - recommendNew_ids = recommendNew_ids.recommend_article_ids - else : - userType = await UserTypeService().create_user_type(request, session) - recommendNew_ids = await RecommendRepository().get( - pk=await user_id_to_classification_id(user_id=userType.user_id, session=session), - session=session) - recommendNew_ids = recommendNew_ids.recommend_article_ids + userType = calculate_user_type(request, UserTypeService().get_questionnaire_data()) + recommendNew_ids = await RecommendRepository().get( + pk=await user_type_to_classification_id(userType), session=session + ) + recommendNew_ids = recommendNew_ids.recommend_article_ids recommendNews = [] for id in recommendNew_ids: temp_article = await CrawledArticleRepository().get(pk=id, session=session) - recommendNews.append(ArticleResponseDTO( - id=id, - title=temp_article.simple_title, - content=temp_article.simple_content, - pubDate=temp_article.published_at.strftime("%Y-%m-%d"), - image=temp_article.image_url - )) - if request.id == None: - return GenericResponseDTO[UserTypeResponseDTO]( - data=UserTypeResponseDTO( - userType={'ISSUE_FINDER': userType[0], - 'LIFESTYLE_CONSUMER': userType[1], - 'ENTERTAINER': userType[2], - 'TECH_SPECIALIST': userType[3], - 'PROFESSIONALS': userType[4]}, - recommendNews=recommendNews - ), message="user type created successfully", result=True - ) - else: - return GenericResponseDTO[UserTypeResponseDTO]( - data=UserTypeResponseDTO( - userType={'ISSUE_FINDER': userType.user_type_issue_finder, - 'LIFESTYLE_CONSUMER': userType.user_type_lifestyle_consumer, - 'ENTERTAINER': userType.user_type_entertainer, - 'TECH_SPECIALIST': userType.user_type_tech_specialist, - 'PROFESSIONALS': userType.user_type_professionals}, - recommendNews=recommendNews - ), message="user type created successfully", result=True + recommendNews.append( + ArticleResponseDTO( + id=id, + title=temp_article.simple_title, + content=temp_article.simple_content, + pubDate=temp_article.published_at.strftime("%Y-%m-%d"), + image=temp_article.image_url, + ) ) + + return GenericResponseDTO[UserTypeResponseDTO]( + data=UserTypeResponseDTO( + userType={ + "ISSUE_FINDER": userType[0], + "LIFESTYLE_CONSUMER": userType[1], + "ENTERTAINER": userType[2], + "TECH_SPECIALIST": userType[3], + "PROFESSIONALS": userType[4], + }, + recommendNews=recommendNews, + ), + message="유형검사 결과가 성공", + result=True, + ) diff --git a/app/service/user_type_service.py b/app/service/user_type_service.py index caf73fa..8b7a032 100644 --- a/app/service/user_type_service.py +++ b/app/service/user_type_service.py @@ -1,5 +1,6 @@ -from typing import List, Optional +from typing import List +from fastapi import HTTPException from groq import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -8,7 +9,6 @@ class UserTypeQuestionnaireRequestDTO(BaseModel): - id: Optional[int] answers: List[int] @@ -53,14 +53,31 @@ def set_user_type_questionnaire(questionnaire_data) -> List[UserTypeQuestionnair def calculate_user_type(answers: UserTypeQuestionnaireRequestDTO, questionnaire_data): user_type = [0 for _ in range(5)] + for i in range(10): # answer in answers.answers: - answer_index = int(answers.answers[i]) # answers의 각 항목을 정수로 변환 - user_type_index = int( - questionnaire_data[i][answer_index + 1][2] - ) # 정수 인덱스로 접근 - if user_type_index == UserTypes.NONE: - continue - user_type[user_type_index] += questionnaire_data[i][answer_index + 1][1] + try: + answer_index = int(answers.answers[i]) # answers의 각 항목을 정수로 변환 + + # 인덱스 범위 확인 + if i >= len(questionnaire_data): + raise HTTPException( + detail="10개의 응답을 입력해주세요.", status_code=400 + ) + if answer_index + 1 >= len(questionnaire_data[i]): + raise HTTPException(detail="0부터 2까지 유효합니다.", status_code=400) + + user_type_index = int( + questionnaire_data[i][answer_index + 1][2] + ) # 정수 인덱스로 접근 + + if user_type_index == UserTypes.NONE: + continue + + user_type[user_type_index] += questionnaire_data[i][answer_index + 1][1] + + except IndexError as e: + raise HTTPException(status_code=500, detail=f"인덱스 오류: {e}") from e + return user_type @@ -69,65 +86,70 @@ def __init__(self): self.questionnaire_data = [ [ "최신 경제 이슈에 대해 얼마나 잘 알고 있습니까?", - ["매우 잘 알고 있다.", 10, UserTypes.ISSUE_FINDER.value['id']], - ["다소 알고 있다.", 5, UserTypes.ISSUE_FINDER.value['id']], - ["잘 모른다.", 0, UserTypes.NONE.value['id']], + ["매우 잘 알고 있다.", 10, UserTypes.ISSUE_FINDER.value["id"]], + ["다소 알고 있다.", 5, UserTypes.ISSUE_FINDER.value["id"]], + ["잘 모른다.", 0, UserTypes.NONE.value["id"]], ], [ "경제 뉴스를 얼마나 자주 찾아보십니까?", - ["매일 확인한다.", 10, UserTypes.ISSUE_FINDER.value['id']], - ["주간 단위로 확인한다.", 5, UserTypes.ISSUE_FINDER.value['id']], - ["가끔 확인한다.", 0, UserTypes.NONE.value['id']], + ["매일 확인한다.", 10, UserTypes.ISSUE_FINDER.value["id"]], + ["주간 단위로 확인한다.", 5, UserTypes.ISSUE_FINDER.value["id"]], + ["가끔 확인한다.", 0, UserTypes.NONE.value["id"]], ], [ "경제 관련 논란이나 논쟁에 얼마나 관심이 있습니까?", - ["매우 관심이 있다.", 10, UserTypes.ISSUE_FINDER.value['id']], - ["다소 관심이 있다.", 5, UserTypes.ISSUE_FINDER.value['id']], - ["잘 모른다.", 0, UserTypes.NONE.value['id']], + ["매우 관심이 있다.", 10, UserTypes.ISSUE_FINDER.value["id"]], + ["다소 관심이 있다.", 5, UserTypes.ISSUE_FINDER.value["id"]], + ["잘 모른다.", 0, UserTypes.NONE.value["id"]], ], [ "경제 정보를 어떻게 활용하시나요?", - ["일상 생활에 적용해본다.", 10, UserTypes.LIFESTYLE_CONSUMER.value['id']], - ["흥미로운 정보는 기억한다.", 10, UserTypes.ENTERTAINER.value['id']], - ["크게 활용하지 않는다.", 0, UserTypes.NONE.value['id']], + [ + "일상 생활에 적용해본다.", + 10, + UserTypes.LIFESTYLE_CONSUMER.value["id"], + ], + ["흥미로운 정보는 기억한다.", 10, UserTypes.ENTERTAINER.value["id"]], + ["크게 활용하지 않는다.", 0, UserTypes.NONE.value["id"]], ], [ "절약이나 소비자 팁에 관심이 있으신가요?", - ["매우 관심이 있다.", 10, UserTypes.LIFESTYLE_CONSUMER.value['id']], - ["다소 관심이 있다.", 5, UserTypes.LIFESTYLE_CONSUMER.value['id']], - ["별로 관심이 없다.", 0, UserTypes.NONE.value['id']], + ["매우 관심이 있다.", 10, UserTypes.LIFESTYLE_CONSUMER.value["id"]], + ["다소 관심이 있다.", 5, UserTypes.LIFESTYLE_CONSUMER.value["id"]], + ["별로 관심이 없다.", 0, UserTypes.NONE.value["id"]], ], [ "경제 관련 이야기를 어떻게 즐기시나요?", - ["심도 깊게 분석한다.", 10, UserTypes.PROFESSIONALS.value['id']], - ["가벼운 마음으로 즐긴다.", 5, UserTypes.ENTERTAINER.value['id']], - ["별로 관심이 없다.", 0, UserTypes.NONE.value['id']], + ["심도 깊게 분석한다.", 10, UserTypes.PROFESSIONALS.value["id"]], + ["가벼운 마음으로 즐긴다.", 5, UserTypes.ENTERTAINER.value["id"]], + ["별로 관심이 없다.", 0, UserTypes.NONE.value["id"]], ], [ "기술과 경제의 결합에 대해 얼마나 잘 이해하고 있습니까?", - ["매우 잘 이해한다.", 10, UserTypes.TECH_SPECIALIST.value['id']], - ["다소 이해한다.", 5, UserTypes.TECH_SPECIALIST.value['id']], - ["잘 모른다.", 0, UserTypes.NONE.value['id']], + ["매우 잘 이해한다.", 10, UserTypes.TECH_SPECIALIST.value["id"]], + ["다소 이해한다.", 5, UserTypes.TECH_SPECIALIST.value["id"]], + ["잘 모른다.", 0, UserTypes.NONE.value["id"]], ], [ "기술 발전이 경제에 미치는 영향에 대해 얼마나 알고 있습니까?", - ["깊이 있는 지식이 있다. ", 10, UserTypes.TECH_SPECIALIST.value['id']], - ["일반적인 이해만 한다. ", 5, UserTypes.TECH_SPECIALIST.value['id']], - ["잘 모른다.", 0, UserTypes.NONE.value['id']], + ["깊이 있는 지식이 있다. ", 10, UserTypes.TECH_SPECIALIST.value["id"]], + ["일반적인 이해만 한다. ", 5, UserTypes.TECH_SPECIALIST.value["id"]], + ["잘 모른다.", 0, UserTypes.NONE.value["id"]], ], [ "전문가 의견이나 통계 데이터에 관심이 있으신가요?", - ["매우 관심이 있다.", 10, UserTypes.PROFESSIONALS.value['id']], - ["다소 관심이 있다.", 5, UserTypes.PROFESSIONALS.value['id']], - ["별로 관심이 없다. ", 5, UserTypes.ENTERTAINER.value['id']], + ["매우 관심이 있다.", 10, UserTypes.PROFESSIONALS.value["id"]], + ["다소 관심이 있다.", 5, UserTypes.PROFESSIONALS.value["id"]], + ["별로 관심이 없다. ", 5, UserTypes.ENTERTAINER.value["id"]], ], [ "경제 분석을 얼마나 자주 읽거나 들으시나요?", - ["자주 읽거나 듣는다.", 10, UserTypes.PROFESSIONALS.value['id']], - ["가끔 읽거나 듣는다.", 5, UserTypes.PROFESSIONALS.value['id']], - ["별로 읽거나 듣지 않는다.", 5, UserTypes.ENTERTAINER.value['id']], + ["자주 읽거나 듣는다.", 10, UserTypes.PROFESSIONALS.value["id"]], + ["가끔 읽거나 듣는다.", 5, UserTypes.PROFESSIONALS.value["id"]], + ["별로 읽거나 듣지 않는다.", 5, UserTypes.ENTERTAINER.value["id"]], ], ] + def get_questionnaire_data(self): return self.questionnaire_data @@ -148,19 +170,21 @@ async def create_user_type( if col == max_user_type: user_type_id = idx for user_type in UserTypes: - if user_type.value['id'] == user_type_id: + if user_type.value["id"] == user_type_id: user_type_enum = user_type return await UserTypeRepository().create( user_type=UserType( user_id=answers.id, - user_type_issue_finder=user_types[UserTypes.ISSUE_FINDER.value['id']], + user_type_issue_finder=user_types[UserTypes.ISSUE_FINDER.value["id"]], user_type_lifestyle_consumer=user_types[ - UserTypes.LIFESTYLE_CONSUMER.value['id'] + UserTypes.LIFESTYLE_CONSUMER.value["id"] + ], + user_type_entertainer=user_types[UserTypes.ENTERTAINER.value["id"]], + user_type_tech_specialist=user_types[ + UserTypes.TECH_SPECIALIST.value["id"] ], - user_type_entertainer=user_types[UserTypes.ENTERTAINER.value['id']], - user_type_tech_specialist=user_types[UserTypes.TECH_SPECIALIST.value['id']], - user_type_professionals=user_types[UserTypes.PROFESSIONALS.value['id']], - user_type=user_type_enum.value['name'] + user_type_professionals=user_types[UserTypes.PROFESSIONALS.value["id"]], + user_type=user_type_enum.value["name"], ), session=session, )