o
    Jh                  	   @   s  d dl Z d dlmZmZmZ d dlmZ d dlZd dlmZ d dl	Z	d dl
Z
d dlZd dlmZ d dlmZmZ d dlmZmZ d dlmZ d dlZd d	lmZ d d
lmZmZ d dlmZ d dlZd dlmZ d dlZd dlmZ d dl Z d dl!m"Z"m#Z#m$Z$m%Z% d dl&m'Z' d dlm(Z( d dl)Z)d dl*m+Z+ d dl,Z-ej.ddedddZ/dZ0dZ1e/2dZ3e/2dZ4e/2dZ5ee_6eedZ7e	7dZ8dZ9e Z:dd Z;de<d e=e<e>f fd!d"Z?de<d e=e<e>f fd#d$Z@d%d& ZAd'eBe< d e<fd(d)ZCde<d e<fd*d+ZDd,e<d eBe=e<e<eBeE f  fd-d.ZFd/e<d0eBeE d e<fd1d2ZGd/e<d3eBeH d e<fd4d5ZId/e<d0eBeE d e<fd6d7ZJde<d e+e<e<f fd8d9ZKde<d e<fd:d;ZLde<d e<fd<d=ZMd>d? ZNde<d@e<fdAdBZOe:PdCdDdE ZQe:RdFdGefdHdIZSe:RdJdKedLefdMdNZTe:PdOdPdQ ZUe:PdRdSeEfdTdUZVe:RdVdWeHfdXdYZWe:PdZdSeEfd[d\ZXe:Yd]d^e<dSeEfd_d`ZZG dadb dbeZ[G dcdd dde[Z\e:Pdedfdg Z]e:Pdhedifd^e<fdjdkZ^e:RdldWe[fdmdnZ_e:`dodWe\fdpdqZae:Pdrdsdt Zbe:PdudveEfdwdxZce:Rdye(difd^e<fdzd{Zde:ed|d}d~ ZfdZgdZhG dd deZiG dd deZjdd Zke:ed|dd Zle:jPddgddd Zme:jRddgddeifddZne:jRddgddejfddZoe:jRddgddd ZpdS )    N)	APIRouterHTTPExceptionQuery)RedirectResponse)OpenAI)BeautifulSoup)
SpeechTextUserQuestion)loggeropenai_api_key)datetime)Settings)create_enginetext)	BaseModel)BackgroundTasks)database_datedatabase_numdatabase_add_qdatabase_chatdefaultdict)Body)Tuple	localhostiA  rest)chroma_api_impl)hostportsettingszsegue_saved_qna_master.xlsxsegue_saved_qna13segue_qna_by_question4segue_profile_split6)api_keys3z$shanri-ai-chatbot-for-text-to-speechc                    s   t jjjdd| d}d| dt  d}t|d}| D ]}|| q W d    n1 s2w   Y  | dt  d}t	|t
| t| dt
 d	| S )
Nztts-1nova)modelvoiceinputz
tmp/audio--z.mp3wbzhttps://z.s3.amazonaws.com/)clientaudiospeechcreatetimeopen
iter_byteswrite	s3_clientupload_filebucket_nameosremove)r   user_idresponse
audio_filefchunks3_key r>   ./home/air/segue/gemini/back/gemini_routerV2.pysynthesize_speechE   s    
r@   questionreturnc                       t d}tjjd| d}|jd j}|j|gdd}|r(|d r(|d d s*dS |d	 d d d
d}|d d d }||fS )Nr    text-embedding-3-smallr&   r(   r      query_embeddings	n_results	documents   情報がありません。g      ?	metadatasanswer 	distances	chroma_clientget_collectionr+   
embeddingsr.   data	embeddingqueryget)rA   
collectionr9   query_embeddingresultsrN   distancer>   r>   r?   search_saved_qna_answerV       
r]   c                    rC   )Nr!   text-embedding-3-largerE   r   rF   rG   rJ   rK   rM   rN   rO   rP   rQ   )rA   collection_qnar9   rZ   r[   rN   r\   r>   r>   r?   search_qna_answerm   r^   ra   c                    s   dddddddddddddddddddd	ddd
dddddddddddddddddddddddd|  dg}t jjjd|d}|jd jjS )u#   homepage_url을 선택하는 함수systemu   質問を分析し、回答を作成する際に必要な情報を持つURLを返す必要があります。質問を見てURLを選択してください。rolecontentuT   最も情報がある可能性が高いURLを配列で返す必要があります。u   関数の戻り値のように、配列のみを返す必要があります。 例) ['https://segue-g.jp/company/boardmember/index.html','https://segue-g.jp/ir/results/settle.html']u9   企業理念 : https://segue-g.jp/company/sdgs/index.htmlu5   IR 情報 : https://segue-g.jp/ir/results/settle.htmlu   経営成績、売上高、営業利益、経常利益、親会社株主に帰属する当期純利益 : https://segue-g.jp/ir/results/index.htmlu   財政状況、総資産、純資産、自己資本比率、１株当たり純資産 : https://segue-g.jp/ir/results/finance.htmlu5   株式情報 : https://segue-g.jp/ir/stock/index.htmluH   当社の強み : https://segue-g.jp/ir/investor/strong_point/index.htmluA   成長戦略 : https://segue-g.jp/ir/investor/strategy/index.htmlu:   会社概要 : https://segue-g.jp/company/basic/index.htmlu:   企業理念 : https://segue-g.jp/company/brand/index.htmlu6   沿革 : https://segue-g.jp/company/history/index.htmluA   役員一覧  : https://segue-g.jp/company/boardmember/index.htmlu5   事業紹介 : https://segue-g.jp/business/index.htmluseru	   質問 : gpt-4or&   messagesr   )r+   chatcompletionsr.   choicesmessagere   )rA   ri   r9   r>   r>   r?   choose_hompage_url   s.   rn   url_listc                    s   d}| D ]J}z+t d|  t|}|r1tj|ddd}|r1|d| d|  d| d7 }W q tyO } zt d	| d
|  W Y d }~qd }~ww |rT|S dS )NrO   u    🌐 Crawling with Trafilatura: FT)include_commentsinclude_tablesz

========== [URL] z ==========
z
========== [END] u   Trafialtura 크롤링 실패 - z: u3   サイト情報が取得できませんでした。)r
   infotrafilatura	fetch_urlextractstrip	Exceptionwarning)ro   all_texturl
downloadedresulter>   r>   r?   crawl_with_urls   s,   
"r~   c                    s   dt  jd  dt  jd  dt  jd  dt  jd  dt  jd  d	t  jd  d	t  j d
t  j dt  jd  dt  j d|  d}tjjjddddd|dgd}|jd jj	
 S )Nuy	  
    以下は、会社に関する２つの情報カテゴリ（Table1とTable2）です。

    [Table1]
    売上高
    売上
    売り上げ
    うりあげ
    営業利益
    営業利益率
    経常利益
    親会社株主に帰属する当期純利益
    1株当たり当期純利益
    自己資本当期純利益率
    総資産
    純資産
    総負債
    自己資本比率
    １株当たり純資産
    営業活動によるキャッシュ・フロー
    投資活動によるキャッシュ・フロー
    財務活動によるキャッシュ・フロー
    現金及び現金同等物の残高

    [Table2]
    沿革
    決算短信
    決算説明資料
    有価証券報告書
    株主総会関連資料
    株主通信
    その他IR情報

    【追加の厳格一致ルール】
    - Table1/2 のキーワードは、質問文にその語が「文字通りに出現」する場合に限り採用する（類義語・言い換え・推測は禁止）。
    - 質問文に該当語が一つも出現しない場合は、意味的に近くても必ず「なし」とする。
    - 「Table1 - 該当項目なし」や「Table2 - 該当項目なし」という形式は出力してはならない。該当がないときは必ず「なし - 推奨リンク: [...]」の形式で返す。
    - 半角/全角や空白の差は正規化して照合するが、語尾変化・類義語置換は行わない。

    ユーザーの質問に対して、次の3つの情報を特定してください：

    1. 該当するテーブル名（Table1 または Table2。どちらにも該当しない場合は「なし」）
    2. 該当するキーワード（例：「売上高」「営業利益率」「有価証券報告書」など。該当しない場合は「該当項目なし」）
    3. 質問文に明示された西暦年、または以下の表現から自然に推定できる西暦年の配列：

    ※ 年が明示されている場合（例：「2023年」「2024年度」など）は、必ずその年を優先して使用してください。
    ※ 明示された年と、「過去3年」「昨年」「前年同月比」「改善」「推移」などが両方含まれる場合は、明示された年を中心に考え、その他の年は補足的に扱って構いません。

    - 「過去3年」「過去5年」など → 現在を含む直近    u   〜rF   u:    年を推定
    - 「昨年」「前年同月比」 → u    年
    - 「一昨年」 →    u    年
    - 質問文に「改善」「推移」「変化」「動向」「変遷」「増減」などの言葉が含まれており、かつ年が明示されていない場合は、直近3年間（, u   ）を推定して構いません。
    - 質問文に「比較」「前年比」「前年同月比」「比べて」「差」などの言葉が含まれている場合は、今年（u   年）とその前年（u   年）を両方含めてください。
    - Table2に該当し、かつ質問文に年の記載がない場合は、2014年から現在（u  年）までのすべての年を対象として構いません。

    ---

    さらに、Table1にもTable2にも該当しない場合（"なし"と判定した場合）は、以下の候補リンクの中から「質問内容に関係がありそうなページURL」を配列形式で推薦してください。複数選んでも構いません。

    [候補リンク一覧]
    「会社の設立年・所在地・資本金・従業員数・事業内容など、基本的な企業情報を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/basic/index.html
    「経営トップからのメッセージや企業のビジョン・経営方針を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/message/index.html
    「企業理念・ブランドスローガン・存在意義（パーパス）を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/brand/index.html
    「代表取締役や取締役など、経営陣の氏名・役職を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/boardmember/index.html
    「グループ会社の名称・所在地・事業内容など、グループ全体の構成を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/group/index.html
    「当社のSDGs（持続可能な開発目標）への取り組み内容や社会貢献活動を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/company/sdgs/index.html
    「当社の主要事業領域や提供サービス、自社開発製品（RevoWorks等）の概要を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/business/index.html
    「セグエグループの中期経営計画や成長戦略、将来ビジョンに関する質問」の場合、このURLを選択すべきです。 - https://segue-g.jp/ir/investor/strategy/index.html
    「当社の競争優位性や強み（技術力、顧客基盤、製品特徴など）を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/ir/investor/strong_point/index.html
    「取締役会の構成・監査体制・コンプライアンス方針など、企業統治（コーポレート・ガバナンス）に関する情報を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/ir/governance/index.html
    「社債の発行状況や格付機関による信用格付情報を知りたい場合」に選択すべきURLです。 - https://segue-g.jp/ir/stock/rating.html
    「「当社の主要事業領域や提供サービス、自社開発製品（RevoWorks等）の概要」に選択すべきURLです。 - https://revoworks.jp/about/
    「RevoWorks製品の具体的な導入事例や活用実績、顧客の声を知りたい場合」に選択すべきURLです。 - https://revoworks.jp/media/case-study

    ---

    【出力形式】

    1. Tableに該当する場合:
    Table名 - キーワード - [西暦年リスト]

    2. 該当しない場合（なし）:
    なし - 推奨リンク: [リンク1, リンク2, ...]

    ---

    【出力例】
    Table1 - 売上高 - [2022, 2023, 2024]
    Table1 - 売上高, 営業利益率 - [2023]
    Table2 - 有価証券報告書 - [2023]
    なし - 推奨リンク: [https://segue-g.jp/company/basic/index.html, https://segue-g.jp/business/index.html]

    ---

    質問文:
    z
    rg   rb   u   あなたは質問文を分類するAIアシスタントです。質問がどのテーブルに該当するか判断してください。rc   rf   rh   r   )r   nowyearr+   rj   rk   r.   rl   rm   re   rv   )rA   promptr9   r>   r>   r?   classification_qustion   s>   0012333445_br   r|   c              
      s   zFt d| }|sg W S |d dd |ddD }|dp)|dp)d	 }d
d t d|D |s<g W S  fdd|D W S  tyb } ztd|  g W  Y d}~S d}~ww )u   
    例:
      "Table1 - 売上高 - [2022, 2023]"
      "Table1 - 売上 - [該当項目なし]"
      "Table1 - 売上高 - 該当項目なし"   ← 대괄호 없어도 처리
    → [("Table1","売上高",[...]), ...]
    z2^(Table[12])\s*-\s*(.+?)\s*-\s*(?:\[(.*?)\]|(.*))$rF   c                 S   s(   g | ]}|  r|  d kr|  qS )u   該当項目なしrv   .0ir>   r>   r?   
<listcomp>1  s    z+parse_classified_result.<locals>.<listcomp>r   ,r      rO   c                 S      g | ]}t |qS r>   )int)r   yr>   r>   r?   r   9      z\d{4}c                    s   g | ]} |fqS r>   r>   )r   	indicator
table_nameyearsr>   r?   r   ?  s    u    分類結果の解析エラー: N)	rematchgroupsplitrv   findallrw   r
   rx   )r|   m
indicators	years_strr}   r>   r   r?   parse_classified_result!  s$   
r   r   r   c              
      sv  ddddd}| | | } |st j}|d |d |g}i dddddd	d
dddddddddddddddddddddd d!d"d#}i dd$dd$dd%d
d$dd$dd&dd%dd$dd$dd$dd%dd&dd$dd$d d$d"d$}| | }|sd'|  d(S |\}}| | d)}	d*| d+}
tj|
d,|id-I d H }|sd'|  d.S d/d0lm} |t}|D ]}||d1  	|d2 |d3 f qd4|  d5}d6}|d5| d57 }|d7|	 d87 }t
| D ]A}t|| D ]8\}\}}|d/kr| d9nd:}|	d%kr|d;| d<| d<|d=d>7 }q|d;| d<| d<t|d?d>7 }qq|d@| dA7 }|S )BN	   売上高)u   売上u   売り上げu	   売上額u   うりあげr   rF   )sales_revenue(https://segue-g.jp/ir/results/index.htmlu   営業利益)operating_profitr   u   営業利益率)operating_profit_marginr   u   経常利益)ordinary_profitr   u-   親会社株主に帰属する当期純利益)!net_income_attributable_to_ownersr   u   1株当たり当期純利益)earnings_per_sharer   u   自己資本当期純利益率)return_on_equityr   u	   総資産)total_assets*https://segue-g.jp/ir/results/finance.htmlu	   純資産)
net_assetsr   u	   総負債)total_liabilitiesr   u   自己資本比率)equity_ratior   u   １株当たり純資産)net_assets_per_sharer   u0   営業活動によるキャッシュ・フロー)cash_flow_operating%https://segue-g.jp/ir/results/cf.htmlu0   投資活動によるキャッシュ・フロー)cash_flow_investingr   u0   財務活動によるキャッシュ・フロー)cash_flow_financingr   u$   現金及び現金同等物の残高)cash_and_equivalentsr   	   百万円%u   円   「uK   」に対応するデータテーブルが見つかりませんでした。u   金額3
        SELECT year, quarter, amount
        FROM j   
        WHERE year IN :years
        ORDER BY year, FIELD(quarter, '1Q', '2Q', '3Q', '4Q', '合計')
    r   rW   values<   」に関するデータが見つかりませんでした。r   r   r   quarteramount
### 
   弊社の2025年12月期第1四半期の売上高は47億8,800万円。第2四半期累計では前年同期比18.9%増の100億円に達しました。通期業績予想は売上高248億円（前期比＋32.5%）へ上方修正されています。   | 年度 | 四半期 | 金額 (') |
|------|--------|----------------|
   年rO   |  | z.2f |
r      
参考リンク: []
)rX   r   r   r   r   	fetch_allcollectionsr   listappendsortedkeys	enumerater   )r   r   	alias_mapr   	table_mapunit_maptable_entryr   reference_urlunitrW   rowsr   groupedrowmdnoter   idxr   r   
year_labelr>   r>   r?   fetch_table1_indicator_markdownF  s   
	
	
 
 $r   r   c                    s&  t t}t }|D ]<}|d }d|v r|d r|d nd td|}|r3t|d}|| |  rEt fdddD sE|	  q	d	|  d
}t
|D ]-}|d| d7 }|| D ]}| dD ]}	|	 }	|	rw|d|	 d7 }qgq^|d7 }qP|d7 }t
|D ]
}
|d|
 d7 }q|S )NoutputURLrO   u
   (\d{4})年rF   c                 3       | ]}| v V  qd S Nr>   )r   tagrz   r>   r?   	<genexpr>      z'format_table2_output.<locals>.<genexpr>)z<az</a>zhref=z<h3>z</h3>
z<h4>u	   年</h4>
r   u   ・z<br>
z	<br><br>
u   
参考リンク一覧:
z- )r   r   setr   searchr   r   r   anyaddr   rv   r   )r   r   r   url_setr   r   r   r   r   lineur>   r   r?   format_table2_output  s6   

r   c           	   
      s    st  j}ttd|d  ddddddd	d
}| |vr%d|  dS ||  }d| d}z!tj|dI d H } fdd|D }|sKd|  dW S t| |W S  tyq } zt	
d|  d d|  dW  Y d }~S d }~ww )Ni  rF   Company_Historyfinancial_summary_reports financial_presentation_materialssecurities_reportsshareholder_meeting_documentsshareholder_newslettersother_ir_information)u   沿革u   決算短信u   決算説明資料u   有価証券報告書u   株主総会関連資料u   株主通信u   その他IR情報r   uK   」に対応するTable2のテーブルが見つかりませんでした。z/
        SELECT output, URL, date
        FROM z
        ORDER BY id
    )rW   c                    s&   g | ] t  fd dD r qS )c                 3   s$    | ]}t |t  d  v V  qdS )dateNstr)r   r   r   r>   r?   r     s   " z=fetch_table2_indicator_markdown.<locals>.<listcomp>.<genexpr>)r   )r   r   r   r?   r     s    z3fetch_table2_indicator_markdown.<locals>.<listcomp>r   u    Table2データ取得エラー ()uH   」に関するデータの取得中にエラーが発生しました。)r   r   r   r   ranger   r   r   rw   r
   	exception)	r   r   r   
TABLE2_MAPr   rW   r   filtered_rowsr}   r>   r   r?   fetch_table2_indicator_markdown  s>   


r   c                    s(  ddl }ddl}d}|jd| |jds| d|   n|   fdd}t|I dH }t|d	dpBt|d
drA|jd jd j	nd}|}d}|d|}	|	r`|	
d}||	
dd }n,|d|}
|
rz||

d}t|tr|

d}||

dd }W n   Y | | fS )u   
    Responses API + web_search 툴을 사용해
    - bullet 요약(최대 10줄)
    - JSON 배열 형태의 출처 [{title, url}] 생성
    를 한 번에 받아온 뒤, (summary, sources) 튜플로 리턴
    r   Nu!   セグエグループ株式会社u/   (セグエグループ|Segue\s?Group|セグエ))flags c                      s&   t jjdddigd  d ddS )Nrg   type
web_searchu  
You are a research assistant. Search the web and return:
1) A concise bullet-point summary (<= 10 lines) answering the user's question using the latest info.
2) Then output a JSON array named SOURCES with objects of shape: {"title": "...", "url": "..."}, 5 items max.

Requirements:
- The topic is "セグエグループ株式会社" (Segue Group). If the question text lacks the company name, include it in your search query.
- Prefer official and primary sources. Prioritize domains: segue-g.jp, revoworks.jp. If you must use others, prefer EDINET, JPX, Nikkei, and government/regulatory sites.
- When possible, use search operators like: (site:segue-g.jp OR site:revoworks.jp).
- SOURCES must be high-quality, non-duplicate, and include at least one official page when available.

Question: r   i   )r&   toolsr(   max_output_tokens)r+   	responsesr.   rv   r>   question_for_searchr>   r?   _call  s   z!web_search_context.<locals>._calloutput_textr   rO   z[]zSOURCES\s*:\s*(\[[\s\S]*?\])rF   z```json\s*([\s\S]*?)```)r   jsonr   Iasyncio	to_threadgetattrr   re   r   r   replacerv   loads
isinstancer   )rA   r   r	  
company_kwr  respr   summarysources_strr   m2_objr>   r  r?   web_search_context  s4   .


r  c              
      sR  d}d}| |krdS | |krd S d}dddd}t  j}|d	 |d
 |g}d|d  d}tj|d|idI d H }|sCd| dS tt}	|D ]}
|	|
d  |
d |
d f qId| d}|d|d  d7 }t|		 D ]+}t
|	| D ]"\}\}}|dkr| dnd}|d| d| dt|dd 7 }qyqq|d!|d"  d#7 }|S )$Nu4   最近3年の売上高推移を教えてください!   売上高を教えてくださいr   r   r   r   r   )db_tabler   r   r   rF   r   r  r   r   r   r   u0   」 관련 데이터를 찾을 수 없습니다.r   r   r   r   r   r   r   r   r   r   rO   r   r   r   r   r   r   r   )r   r   r   r   r   r   r   r   r   r   r   r   )rA   target_questiontarget_question2r   indicator_infor   r   rW   r   r   r   r   r   r   r   r   r   r>   r>   r?   handle_specific_queries8  s@   
 $r  c                    s6  t | I d H }|r|S t| I d H }td|  h d}| |v r#d}|dr<t| I d H \}}td| dt|  |dk rE|S d| v rQtd 	 d	S t| I d H \}}td
| dt|  |dk rm|S t	
d|}|r:|d}	td|	  z1dd |	dD }
dd |
D }dd| d}td|  t|}td|  W n ty } ztd|  W Y d }~dS d }~ww t|I d H }t| I d H \}}td t| td t| ddddddddddd ddd!ddd"ddd#ddd$| ddd%| d&| dd'| dg
}td( tjjjd)|d*}|jd+ jj S dS t|}|set| I d H \}}|dk rS|S t| I d H \}}|dk rc|S dS g }|D ],\}}}|d,kr|t||I d H }n|d-krt||I d H }nd.| }|| qid/|S )0Nu   분석결과 : >      株主総会はいつですか$   株主総会はいつありますか'   株主総会はいつ開かれますか*   株主総会はいつ開催されますかr  u   なしu$   📊 財務類似質問 - distance: z
, answer: 皙?u   株価uI   📈 '株価' 키워드를 감지하여 고정 답변을 반환합니다.u  恐れ入りますが、株価は常に変動しているため、
最新の株価については以下でご確認ください：

https://finance.yahoo.co.jp/search/?query=%E3%82%BB%E3%82%B0%E3%82%A8%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97

https://segue-g.jp/ir/index.html
u!   🎯 保存済みQnA - distance: u)   なし\s*-\s*推奨リンク:\s*\[(.*?)\]rF   u1   📦 추출된 URL 리스트 문자열 (원본): c                 S   s   g | ]}|  qS r>   r   r   r   r>   r>   r?   r     r   z'generate_gpt_answer.<locals>.<listcomp>r   c                 S   s0   g | ]}| d s| dsd | d n|qS )"')
startswithr#  r>   r>   r?   r     s   0 [r   ]u1   📦 변환된 URL 리스트 문자열 (quoted): u%   🧾 최종 파싱된 URL 리스트: u   URL 파싱 실패: rL   u   === 🕸 Web Search Summary ===u%   === 🕸 Web Search Sources(JSON) ===rb   uu   あなたは「セグエグループ」の会社紹介および就職希望者向けのチャットボットです。rc   uK   以下のルールに従って、ユーザーと会話してください。ub   1.会話スタイル：丁寧で信頼感のあるビジネス口調で応答してください。uk   2.トーン：敬語を用いながらも、丁寧で親しみやすい表現を心がけてください。u   3.回答制限：外部サイトへのリンクは絶対に出力してはいけません。リンクを案内する場合は必ず https://segue-g.jp/index.html のみを使用してください。uq   4.数値データ形式：数値がある場合は、マークダウン形式の表で表示してください。u   5.リンクを表記する際はマークダウン形式([テキスト]URL)を使わず、必ずURLのみをそのまま記載してください。末尾に括弧や句読点を付けないでください。u   企業サイト情報:
u    ウェブ検索結果の要約:
u   

参考ソース(JSON):
rf   u8   🕸 GPTにクロール結果を元に回答生成開始rg   rh   r   Table1Table2u    不明なテーブルタイプ: z

) r  r   printr&  ra   r
   rr   reprr]   r   r   r   r   joinastliteral_evalrw   rx   r~   r  r+   rj   rk   r.   rl   rm   re   rv   r   r   r   r   )rA   special_answerclassified_resultOVERRIDE_QUESTIONS
qna_answerqna_distancesaved_answersaved_distance	url_matchurl_list_strurlsquoted_urlssafe_strro   r}   website_dataweb_summaryweb_sources_jsonri   r9   parsedanswersr   r   r   rN   r>   r>   r?   generate_gpt_answerj  s   !









rA  c               
      sr  zt dt d tt} | jddgdd | d  }| d  }|s;t d tj	t
d tjt
d W d S tjjd	|d
}dd |jD }ztj	t
d W n	 ty\   Y nw tjt
d}|jdd tt|D ||dd |D d t dt
 dt| d W d S  ty   t dt  Y d S  ty } zt jd| dd W Y d }~d S d }~ww )Nr%  u:   ' 파일에서 ChromaDB 새로고침을 시작합니다...rA   rN   T)subsetinplaceu7   엑셀에 데이터가 없어 ChromaDB를 비웁니다.namerD   rE   c                 S      g | ]}|j qS r>   rV   r   rU   r>   r>   r?   r         z,reload_chroma_from_excel.<locals>.<listcomp>c                 S   r   r>   r   r   r>   r>   r?   r   $  r   c                 S   s   g | ]}d |iqS rN   r>   )r   ansr>   r>   r?   r   '  r   idsrT   rJ   rM   u   ✅ ChromaDB 'u   ' 컬렉션을 u*   개 데이터로 새로고침했습니다.u9   🚨 마스터 엑셀 파일을 찾을 수 없습니다: u.   🚨 ChromaDB 새로고침 중 에러 발생: exc_info)r
   rr   EXCEL_FILE_PATHpd
read_exceldropnatolistrx   rR   delete_collectionCOLLECTION_NAMEcreate_collectionr+   rT   r.   rU   rw   r   r   lenFileNotFoundErrorerror)df	questionsr@  r9   rT   rY   r}   r>   r>   r?   reload_chroma_from_excel  sH   

""r]  rN   c           
   
      s|  t  fdddD rd S ztd|  d tjjd| d}|jd j}tj	t
d	}|j|gd
d}|r^|dr^|d d r^|d d d dk r^td|d d d dd W d S tdt d t|  dg}ztt}tj||gdd}W n ty   |}Y nw |jtdd td t I d H  W d S  ty }	 ztjd|	 dd W Y d }	~	d S d }	~	ww )Nc                 3   r   r   r>   )r   xrJ  r>   r?   r   2  r   z.add_qna_to_excel_and_reload.<locals>.<genexpr>)u	   申し訳u   情報がありませんu   わかりかねますr%  u5   '과 유사한 질문이 있는지 확인합니다...rD   rE   r   rD  rF   rG   rP   g333333?uN   유사한 질문이 이미 존재하여 저장하지 않습니다. (Distance: .4fr   u   새로운 Q&A를 'u   '에 추가합니다...rA   rN   Tignore_indexFindexu2   ✅ 엑셀 파일에 저장을 완료했습니다.u-   🚨 엑셀에 Q&A 저장 중 에러 발생: rN  )r   r
   rr   r+   rT   r.   rU   rV   rR   rS   rV  rW   rX   rP  rQ  	DataFramerR  concatrY  to_excelr]  rw   rZ  )
rA   rN   r9   rV   rY   r[   new_datar[  
updated_dfr}   r>   rJ  r?   add_qna_to_excel_and_reload1  sB   . 

"rj  z/healthc                      s
   ddiS )Nstatushealthyr>   r>   r>   r>   r?   health_check^  s   rm  z/apige/speechspeech_textc                    s6   | j }| j}|stdddt||I d H }d|iS )N  zText is requiredstatus_codedetailr:   )r   
chat_tokenr   r@   )rn  r   rs  r:   r>   r>   r?   r-   c  s   r-   z/apige/ask_questionuser_questionbackground_tasksc                    s   | j  }| j }|r|stdddd}tj|d|idI d H }|s8d}tj||d d |d	d
I d H }t|I d H }d}tj|d|idI d H pNd}	d}
tj|
|d||	d d|d||	d dgd
I d H  |	t
|| d|iS )Nro  u'   question と chat_token が必要ですrp  z.SELECT id FROM chats WHERE chat_token = :tokentokenr   z_
            INSERT INTO chats (title, chat_token)
            VALUES (:title, :token)
           )titlerv  r   zBSELECT MAX(sort_order) FROM chat_messages WHERE chat_id = :chat_idchat_idr   z
        INSERT INTO chat_messages (chat_id, role, message, sort_order)
        VALUES (:chat_id, :role, :message, :sort_order)
    rf   rF   )rz  rd   rm   
sort_order	assistantr   rN   )rA   rv   rs  r   r   	fetch_valexecuterA  execute_manyadd_taskrj  )rt  ru  question_textrs  rW   rz  insert_chat_queryrN   
sort_querycurrent_sortinsert_queryr>   r>   r?   gemini_questionm  s2   

r  z/apige/get_questionsc               
      sP   zd} t | I d H }dd |D W S  ty' } ztdt|dd }~ww )Nz+SELECT id, question FROM `list` ORDER BY idc                 S      g | ]}|d  |d dqS )idrA   )r  rA   r>   r   r   r>   r>   r?   r         z!get_questions.<locals>.<listcomp>  rp  r   r   rw   r   r   rW   r   r}   r>   r>   r?   get_questions  s   r  z/apige/get_answerr  c              
      sp   z"d}t j|d| idI d H }|r|d |d pddW S dddW S  ty7 } ztdt|d	d }~ww )
Nz2SELECT question, answer FROM `list` WHERE id = :idr  rw  rA   rN   rO   r`  r  rp  )r   	fetch_onerw   r   r   )r  rW   r   r}   r>   r>   r?   
get_answer  s   r  z/apige/save_questionrU   c              
      sp   |  d}|  d}zd| d}tj|d|idI d H  dddW S  ty7 } ztd	t|d
d }~ww )NtablerA   zINSERT INTO `z` (Q) VALUES (:question)rw  Tu!   データが保存されましたsuccessrm   r  rp  )rX   r   r~  rw   r   r   )rU   r  rA   rW   r}   r>   r>   r?   save_question  s   

r  z/apige/questions/{id}c              
      sb   d|  }zd| d}t |I d H }dd |D W S  ty0 } ztdt|dd }~ww )NQSELECT id, Q FROM `z` ORDER BY id ASCc                 S   r  )r  r  )r  r  r>   r  r>   r>   r?   r     r  z"get_table_data.<locals>.<listcomp>r  rp  r  )r  r   rW   r   r}   r>   r>   r?   get_table_data  s   
r  z/apige/delete_questionr  c              
      sf   zd|  d}t j|d|idI d H }dddW S  ty2 } zdt|dW  Y d }~S d }~ww )	NzDELETE FROM `z` WHERE id = :id LIMIT 1r  rw  Tu   削除しましたr  F)r   r~  rw   r   )r  r  rW   r|   r}   r>   r>   r?   delete_question  s   r  c                   @   s.   e Zd ZU eed< eed< eed< eed< dS )
InsertDatar  r   re   r   N__name__
__module____qualname__r   __annotations__r>   r>   r>   r?   r    s
   
 r  c                   @      e Zd ZU eed< dS )
UpdateDatar  N)r  r  r  r   r  r>   r>   r>   r?   r       
 r  z/apige/get_tablesc               
      sL   zt dI d H } dd | D W S  ty% } ztdt|dd }~ww )NzSHOW TABLESc                 S   s   g | ]
}t | d  qS )r   )r   r   r  r>   r>   r?   r     s    zget_tables.<locals>.<listcomp>r  rp  r   r   rw   r   r   )r   r}   r>   r>   r?   
get_tables  s   r  z/apige/get_rows.c              
      sV   zd|  }t |I d H }dd |D W S  ty* } ztdt|dd }~ww )NzSELECT * FROM c                 S   r   r>   )dictr  r>   r>   r?   r     r   zget_rows.<locals>.<listcomp>r  rp  r  )r  rW   r   r}   r>   r>   r?   get_rows  s   
r  z/apige/insert_rowc              
      s   t |  z8d| j d| j d| j d}| j| j| jdddd}tj||d	I d H  t d
| t d| d| j diW S  tyQ } ztdt	|dd }~ww )Nz
            INSERT INTO `z(` (`date`, `content`, `URL`, `output`, `z5`, `zN6`)
            VALUES (:date, :content, :URL, :output, :col5, :col6)
        rO   r      あ)r   re   r   r   col5col6rw  u   실행 쿼리:u   실행 데이터:rm   u1    테이블에 데이터가 추가되었습니다.r  rp  )
r+  r  r   re   r   r   r~  rw   r   r   rU   rW   r   r}   r>   r>   r?   
insert_row  s2   

r  z/apige/update_rowc              
      s   z:d| j  d| j  d| j  d}| j| j| jddd| jd}td	| td
| tj||dI d H  d| j  diW S  tyV } ztdt	| t
dt	|dd }~ww )Nz
            UPDATE `z`
            SET
                `date` = :date,
                `content` = :content,
                `URL` = :URL,
                `output` = :output,
                `z5` = :col5,
                `z.6` = :col6
            WHERE id = :id
        rO   r   r  )r   re   r   r   r  r  r  u   실행쿼리:u   실행데이터:rw  rm   u1    테이블의 데이터가 수정되었습니다.zUpdate Error:r  rp  )r  r   re   r   r  r+  r   r~  rw   r   r   r  r>   r>   r?   
update_row   s4   
	
r  z/apige/chat_listc               
      sV   d} zt | I d H }dd |D W S  ty* } ztddt| dd }~ww )Nzb
        SELECT id, title, chat_token, created_at
        FROM chats
        ORDER BY id DESC
    c                 S   *   g | ]}|d  |d |d |d dqS )r  ry  rs  
created_at)r  ry  rs  r  r>   r  r>   r>   r?   r   P     * z!get_chat_list.<locals>.<listcomp>r  u2   チャット一覧の取得に失敗しました: rp  r   r   rw   r   r   r  r>   r>   r?   get_chat_listG  s   r  z/apige/chat_messages/{chat_id}rz  c              
      s^   d}zt j|d| idI d H }dd |D W S  ty. } ztddt| dd }~ww )	Nz
        SELECT role, message, sort_order, created_at
        FROM chat_messages
        WHERE chat_id = :chat_id
        ORDER BY sort_order ASC
    rz  rw  c                 S   r  )rd   rm   r{  r  )rd   rm   r{  r  r>   r  r>   r>   r?   r   _  r  z%get_chat_messages.<locals>.<listcomp>r  u;   チャットメッセージの取得に失敗しました: rp  r  )rz  rW   r   r}   r>   r>   r?   get_chat_messagesU  s   r  z/apige/saveVectorc              
      s  | r|  dr| dd   stdddt| dd  }ztjdd|idI d H }|r1|d	 s:d
d| diW S |d	  }d|  d}t|I d H }|sXd
|  diW S d}|D ]F}|d }|d  }	|	skq\tj	j
d|	d}
|
jd j}tj|  d| g|	g|g|| dgd tjd|  dd|idI d H  |d7 }q\d
|  d| d| diW S  ty } ztd tdt|dd }~ww )Nr  rF   ro  u   無効なテーブル名ですrp  z&SELECT answer FROM list WHERE id = :idr  rw  rN   rm   u   listテーブルに id=u+    の共通answerが見つかりません。r  z` WHERE is_saved = 0u.    に保存すべき質問がありません。r   r_   rE   _)rN   r  )rM  rJ   rT   rM   zUPDATE `z!` SET is_saved = 1 WHERE id = :idu    の質問 u,    件を保存しました（共通answer id=u
    使用）u,   💥 ベクトル保存中にエラー発生r  )r&  isdigitr   r   r   r  rv   r   r+   rT   r.   rU   rV   r`   r   r~  rw   r
   r   r   )r  table_number
answer_rowcommon_answerrW   r   success_countr   q_idr  r9   rV   r}   r>   r>   r?   save_vector_to_chromag  s^   


r  startupc                      s   t  I dH  dS )uL   서버 시작 시 엑셀 파일 내용으로 ChromaDB를 동기화합니다.N)r]  r>   r>   r>   r?   startup_event  s   r  ztest_qna_with_keywords.xlsxsegue_qna_keywords_testc                   @   s&   e Zd ZU eed< eed< eed< dS )KeywordQnaItemrA   keywordsrN   Nr  r>   r>   r>   r?   r    s   
 r  c                   @   r  )SearchQueryItemrW   Nr  r>   r>   r>   r?   r    r  r  c               
   C   s  zt dt d tt} | jg dddd g }g }g }|  D ]O\}}t|d d}t|d	 d}t|d
 d}|	| |	d|||d |	d| d |	| |	d|||d |	d| d q#|s}t 
d W dS tjjd|d}	dd |	jD }
ztjtd W n	 ty   Y nw tjtd}|j||
||d t dt|  dt| d W dS  ty   t 
dt d tjg ddjtdd Y dS  ty } zt jd| dd  W Y d}~dS d}~ww )!u   
    ✨ [최종 수정] 엑셀 파일을 기준으로 ChromaDB를 동기화합니다.
    ✨ 각 항목에 'type' 메타데이터('question' 또는 'keyword')를 추가하여 분리 검색이 가능하도록 합니다.
    r%  u8   ' 파일을 읽어 테스트 DB를 동기화합니다...)rA   r  rN   Tr   )rB  rC  howrA   z "r  rN   )r   rA   r  rN   	qna_test_	_questionkeyword	_keywordsu1   테스트용 엑셀에 데이터가 없습니다.NrD   rE   c                 S   rF  r>   rG  rH  r>   r>   r?   r     rI  z2sync_keyword_excel_to_chromadb.<locals>.<listcomp>rD  rL  u$   🎉 테스트 DB 동기화 완료! u   개 행에서 u+   개 검색 항목이 로드되었습니다.u;   ' 파일이 없어 빈 샘플 데이터로 시작합니다.)columnsFrc  u/   🚨 테스트 DB 동기화 중 오류 발생: rN  )loggingrr   TEST_EXCEL_PATHrQ  rR  rS  iterrowsr   rv   r   rx   r+   rT   r.   rU   rR   rU  TEST_COLLECTION_NAMErw   rW  r   rX  rY  re  rg  rZ  )r[  all_documents_to_embedall_metadatasall_idsr   r   r  keywords_textanswer_textr9   rT   rY   r}   r>   r>   r?   sync_keyword_excel_to_chromadb  sh   



& "r  c                   C   s
   t   dS )u@   서버 시작 시, 테스트용 DB도 함께 동기화합니다.N)r  r>   r>   r>   r?    run_keyword_test_sync_on_startup  s   
r  z/apige/keyword-test/datazKeyword Search Test)tagsc                  C   s0   zt t} | jddW S  ty   g  Y S w )uB   테스트용 엑셀 파일의 모든 데이터를 반환합니다.records)orient)rQ  rR  r  to_dictrY  )r[  r>   r>   r?   get_all_test_data  s   
r  z/apige/keyword-test/additemc              
   C   s   z3t |  g}zt t}t j||gdd}W n ty$   |}Y nw |jtdd t  ddiW S  t	yG } zt
dt|dd	}~ww )
uQ   새로운 Q&A를 테스트용 엑셀에 추가하고 DB를 새로고침합니다.Tra  Frc  rm   uH   테스트 데이터가 성공적으로 추가 및 반영되었습니다.r  rp  N)rQ  re  r  rR  r  rf  rY  rg  r  rw   r   r   )r  rh  r[  ri  r}   r>   r>   r?   add_test_data  s   

r  z/apige/keyword-test/search
query_itemc              
   C   s  | j s	tdddz| j }tjtd}dddd|dg}tjjjd	|d
d}|j	d j
j }td|  tjjd||gd}|jd j}|jd j}|j |gdddid}	|j |gdddid}
|	d d rs|	d d d ntd}|
d d r|
d d d ntd}td|dd|d d}d}d}d}||krd}|	}td| d | d! n'||krd"}|
}td| d | d! ntd# ||k rd$}|	}nd%}|
}|r|d d sd&d'iW S ||tdkr|nd(|tdkr|nd(|d) d d ||d* d d d|d* d d d+|d* d d d,d-}|W S  tyD } ztjd.| d/d0 td1t|dd}~ww )2u   
    [최종 버전] 사용자 질문을 키워드로 변환하여, '질문'과 '키워드' DB를 분리 검색 후,
    두 거리 값을 모두 포함한 최적의 결과를 반환합니다.
    ro  u   검색어가 없습니다.rp  rD  rb   u}  あなたは、キーワードを抽出するテキスト分析の専門家です。
                以下に提示されるユーザーの質問文から、重要なキーワード「のみ」を抽出することがあなたのタスクです。
                # 厳格なルール
                1. ユーザーの質問文に「実際に含まれている単語」のみを使用してください。
                2. 質問文に存在しない類義語や関連語を絶対に追加しないでください。
                3. 名詞と固有名詞の抽出に集中してください。
                4. 抽出したキーワードは、カンマ区切りのリスト形式で出力してください。
                # 例
                ユーザーの質問: "製品について教えてください。"
                正しい出力: 製品, 教えて
                rc   rf   zgpt-4o-minig        )r&   ri   temperaturer   u   변환된 검색 키워드: rD   rE   rF   r   rA   )rH   rI   wherer  rM  rP   infu   질문 검색 Distance: r_  u    | 키워드 검색 Distance: r"  g333333?Nu   질문 검색u   승자: u    (기준점 u    통과)u   키워드 검색ub   두 검색 모두 기준점을 통과하지 못했습니다. 더 가까운 쪽을 선택합니다.u    질문 검색 (기준점 미달)u#   키워드 검색 (기준점 미달)rm   u   검색 결과가 없습니다.rJ   rM   r  rN   )winnerquestion_distancekeyword_distanceretrieved_documenttransformed_keywordsrA   r  rN   u   🚨 검색 중 오류 발생: TrN  r  )rW   r   rR   rS   r  r+   rj   rk   r.   rl   rm   re   rv   r  rr   rT   rU   rV   floatrX   rw   rZ  r   )r  
user_queryrY   prompt_messageskeyword_responser  r9   query_embedding_for_questionquery_embedding_for_keywordsquestion_resultskeyword_resultsq_distk_distQUESTION_THRESHOLDKEYWORD_THRESHOLDr  final_results
best_matchr}   r>   r>   r?   search_test_data,  sp   $$


r  z/apige/keyword-test/reloadc               
   C   s:   zt   ddiW S  ty }  ztdt| dd} ~ ww )uG   관리자가 수동으로 테스트 DB 새로고침을 명령합니다.rm   ua   테스트 데이터베이스가 엑셀 파일의 최신 내용으로 새로고침되었습니다.r  rp  N)r  rw   r   r   )r}   r>   r>   r?   reload_test_database  s   
r  )qr  fastapir   r   r   fastapi.responsesr   openair   boto3r6   r/   bs4r   schemasr   r	   configr
   r   r   chromadbchromadb.configr   
sqlalchemyr   r   pydanticr   r   r.  r   rs   dbr   r   r   r   r   r   r   r  typingr   pandasrQ  
HttpClientrR   rP  rV  rS   collection_saved_qnar`   collection_profiler#   r+   r3   r5   routerr@   r   tupler  r]   ra   rn   r   r~   r   r   r   r   r  r   r   r  r  rA  r]  rj  rX   rm  postr-   r  r  r  r  r  deleter  r  r  r  r  r  putr  r  r  r  on_eventr  r  r  r  r  r  r  r  r  r  r  r>   r>   r>   r?   <module>   s    




$n%Z$1C2 ,-
	5


	&
E
F

Y