Yuki Kobiyama commited on
Commit
7f68c53
1 Parent(s): 73923a7

fix: refactoring

Browse files
.flake8 ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [flake8]
2
+ max-line-length = 88
README.md CHANGED
@@ -25,3 +25,9 @@ OPENAI_API_KEY =
25
  `pipenv shell`
26
  ## 4. streamlitを立ち上げる
27
  `streamlit run app.py`
 
 
 
 
 
 
 
25
  `pipenv shell`
26
  ## 4. streamlitを立ち上げる
27
  `streamlit run app.py`
28
+
29
+ # TODO
30
+ - 企業ごとの変更点は`form.py`,`embed_output.py`,`utils.py`, `prompts.py`に集約されている想定
31
+ - `utils.py`に必要であれば追加の機能を足すイメージ
32
+ - 上ファイルと`data`部分をフォークのリポジトリ部分で変更する
33
+ - その他のファイルはメインのリポジトリにて変更
app.py DELETED
@@ -1,799 +0,0 @@
1
- import streamlit as st
2
- from datetime import date, datetime
3
- from openai import OpenAI
4
- import os
5
- from dotenv import load_dotenv
6
- import json
7
- from src import embed_output
8
- from src import embed_output_deviation
9
-
10
- from pinecone import Pinecone
11
- import os
12
- from dotenv import load_dotenv
13
-
14
-
15
-
16
- # 変更の種類に沿ってjsonファイルから確認事項の質問を読み取る関数
17
- def question(type, subtype, subtype2):
18
- with open(f'data/questions/{type}_{subtype}_{subtype2}.json', 'r', encoding="utf-8") as file:
19
- data = json.load(file)
20
-
21
- questions = []
22
- for i in data["items"]:
23
- questions.append(i["question"])
24
-
25
- return questions
26
-
27
- def get_embedding(text, model="text-embedding-3-large", client=None):
28
- text = text.replace("\n", " ")
29
- return client.embeddings.create(input=[text], model=model).data[0].embedding
30
-
31
- def change_request():
32
- # タイトル
33
- st.title("変更申請書 文書生成")
34
-
35
- # 環境変数からAPIキーを取得
36
- load_dotenv()
37
- open_api_key = os.environ.get("OPENAI_API_KEY")
38
-
39
- load_dotenv()
40
- pc = Pinecone(os.environ["PINECONE_API_KEY"])
41
- index = pc.Index("bankyo")
42
-
43
- client = OpenAI(api_key=open_api_key)
44
-
45
- # ①申請者などの入力フォーム
46
- with st.form("my_form"):
47
- col1, col2 = st.columns(2)
48
- with col1:
49
- plant_type = st.selectbox("工場", ["", "第一・第二工場","第三工場", "第四工場"])
50
- with col2:
51
- applicant_name = st.text_input("申請者")
52
-
53
- col2_1, col2_2 = st.columns(2)
54
- with col2_1:
55
- change_num = st.selectbox("変更の件数", ["1","2","3","4","5","6","7","8","9","10"])
56
- with col2_2:
57
- application_date = st.date_input("申請日", date.today())
58
-
59
- change_details = st.text_area("変更対象 (変更対象となる製品名・原料名・設備名等を記入する)")
60
-
61
- implementation_date = st.text_area("実施予定時期")
62
-
63
- submit_button0 = st.form_submit_button("進む")
64
- if submit_button0:
65
- st.session_state['form_submitted'] = True
66
-
67
- change_num = int(change_num)
68
-
69
- # セッション状態の初期化
70
- for state in ['form_submitted', 'document_generated']:
71
- if state not in st.session_state:
72
- st.session_state[state] = False
73
- for change in range(change_num):
74
- if f"type_chosen_{change}" not in st.session_state:
75
- st.session_state[f"type_chosen_{change}"] = False
76
- if f"subtype_chosen_{change}" not in st.session_state:
77
- st.session_state[f"subtype_chosen_{change}"] = False
78
-
79
- types = [None] * change_num
80
- subtypes = [None] * change_num
81
- subtypes2 = [None] * change_num
82
-
83
- if st.session_state['form_submitted']:
84
- st.session_state[f"subtype_chosen_-1"] = True
85
-
86
- for change in range(change_num):
87
- # ②変更の種類の選択
88
- if st.session_state[f'subtype_chosen_{change-1}']:
89
- if change_num != 1:
90
- st.markdown(f"**{change+1}つ目の変更**")
91
- # 変更の種類の選択
92
- types[change] = st.selectbox("変更の種類を選択してください:", ["",
93
- "原薬",
94
- "添加物",
95
- "資材変更",
96
- "デザイン変更",
97
- "製造方法・製造設備",
98
- "製造支援システム",
99
- "構造設備・設備移設",
100
- "品質管理",
101
- "出荷・物流"], key = f"type_{change}")
102
- if types[change]:
103
- st.session_state[f'type_chosen_{change}'] = True
104
-
105
- # ③変更内容の選択
106
- if st.session_state[f'type_chosen_{change}']:
107
- # 変更内容の選択
108
- if types[change] == "原薬":
109
- subtypes2[change] = "x"
110
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
111
- "グレード",
112
- "製造設備",
113
- "製造方法",
114
- "製造所(A社→B社)",
115
- "製造場所(製造メーカーに変更はない。A社の①工場→A社の②工場)"], key = f"subtype_1_{change}")
116
- elif types[change] == "添加物":
117
- subtypes2[change] = "x"
118
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
119
- "グレード(型番)",
120
- "製造設備",
121
- "製造メーカー(A社→B社)",
122
- "製造場所(製造メーカーに変更はない。A社の①工場→A社の②工場)"], key = f"subtype_2_{change}")
123
- elif types[change] == "資材変更":
124
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
125
- "チューブ・容器キャップ(直接容器)",
126
- "容器ラベル",
127
- "添付文書",
128
- "個箱",
129
- "セット箱",
130
- "シール(例:見本用シール、変更品シール)",
131
- "パッキングケース",
132
- "パッキングケースラベル"], key = f"subtype_3_{change}")
133
- elif types[change] == "デザイン変更":
134
- subtypes2[change] = "x"
135
- subtypes[change] = "x"
136
- elif types[change] == "製造方法・製造設備":
137
- subtypes2[change] = "x"
138
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
139
- "仕込量変更(スケールアップ・ダウン)",
140
- "標準的仕込量変更(100%仕込み→10●%仕込みに変更)",
141
- "出荷規格の変更",
142
- "調合設備の変更",
143
- "充填設備の変更",
144
- "包装設備の変更",
145
- "調合工程のパラメーター変更",
146
- "充填工程のパラメーター変更(例:充填速度、設定温度、規格範囲、トルク値)",
147
- "包装工程のパラメーター変更",
148
- "重量基準値の変更(ウエイトチェッカーでの計量)",
149
- "包装工程 作業人数の変更",
150
- "洗浄方法の変更"], key = f"subtype_4_{change}")
151
- elif types[change] == "製造支援システム":
152
- subtypes2[change] = "x"
153
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["", "製造支援システム(製造用水、空調システム)"], key = f"subtype_5_{change}")
154
- elif types[change] == "構造設備・設備移設":
155
- subtypes2[change] = "x"
156
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
157
- "構造設備",
158
- "設備の移設(製造設備・試験設備)"], key = f"subtype_6_{change}")
159
- elif types[change] == "品質管理":
160
- subtypes2[change] = "x"
161
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["",
162
- "試験方法の変更",
163
- "試験室の変更",
164
- "外部試験機関の変更",
165
- "試験設備の変更・更新",
166
- "バルク保管期間の変更"], key = f"subtype_7_{change}")
167
- elif types[change] == "出荷・物流":
168
- subtypes2[change] = "x"
169
- subtypes[change] = st.selectbox("変更内容を選択してください:", ["", "製品納品先の変更"], key = f"subtype_8_{change}")
170
-
171
-
172
- if subtypes[change] == "充填工程のパラメーター変更(例:充填速度、設定温度、規格範囲、トルク値)":
173
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["", "液","クリーム","チューブ"], key = f"subtype_9_{change}")
174
-
175
- # 資材変更の3段階目の分岐
176
- match types[change], subtypes[change]:
177
- case "資材変更", "チューブ・容器キャップ(直接容器)":
178
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
179
- "製造設備の変更",
180
- "製造所の変更",
181
- "材質の変更",
182
- "形状変更",
183
- "版番号の変更"], key = f"subtype_3_1_{change}")
184
- case "資材変更", "容器ラベル":
185
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
186
- "版番号の変更",
187
- "セルフメディケーション税制対応マークの追加"], key = f"subtype_3_2_{change}")
188
- case "資材変更", "添付文書":
189
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
190
- "製造所の変更",
191
- "材質変更",
192
- "厚さ変更",
193
- "版番号の変更"], key = f"subtype_3_3_{change}")
194
- case "資材変更", "個箱":
195
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
196
- "製造所の変更",
197
- "捺印位置の変更",
198
- "個箱形状変更",
199
- "個箱の材質(表面加工)",
200
- "版番号の変更",
201
- "セルフメディケーション税制対応マークの追加"], key = f"subtype_3_4_{change}")
202
- case "資材変更", "セット箱":
203
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
204
- "製造所の変更",
205
- "捺印位置の変更",
206
- "セット箱形状変更",
207
- "版番号の変更"], key = f"subtype_3_5_{change}")
208
- case "資材変更", "シール(例:見本用シール、変更品シール)":
209
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
210
- "サイズの変更",
211
- "版番号の変更"], key = f"subtype_3_6_{change}")
212
- case "資材変更", "パッキングケース":
213
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
214
- "版番号の変更"], key = f"subtype_3_7_{change}")
215
- case "資材変更", "パッキングケースラベル":
216
- subtypes2[change] = st.selectbox("詳しい内容を選択してください:", ["",
217
- "版番号の変更"], key = f"subtype_3_8_{change}")
218
-
219
- if subtypes[change] and subtypes2[change]:
220
- st.session_state[f'subtype_chosen_{change}'] = True
221
-
222
- # ④質問の回答からLLMの応答を生成
223
- if st.session_state[f'subtype_chosen_{change_num-1}']:
224
- # システムプロンプトのテンプレ(質問部分以外)
225
- systemprompt_template_1 = f"""
226
- ***
227
- 変更内容:
228
- 変更理由:
229
- 品質への影響:
230
- ***
231
- あなたは、質問に対して入力された情報から、詳しい内容を含んだ申請書の文章を生成するアシスタントです。
232
- 上記の形式に従って、入力された情報を当てはめて出力してください。漏れがないようにしてください。変更がないことに関しては、まとめず主語を明確にして「品質への影響」で言及してください。
233
- 「だ・である」調で出力してください。
234
- あなたは返答をすべてJSON形式で出力します。
235
- ***
236
- """
237
-
238
- systemprompt_template_2 = """
239
- 今回の変更の種類は以下です。
240
- """
241
- for i in range(change_num):
242
- if subtypes2[i] == "x":
243
- systemprompt_template_2 += f"{i+1}. {types[i]},{subtypes[i]}"
244
- else:
245
- systemprompt_template_2 += f"{i+1}. {types[i]},{subtypes[i]},{subtypes2[i]}"
246
- systemprompt_template_2 += "\n"
247
- systemprompt_template_2 += """
248
- ***
249
- あなたが質問した内容は以下です。\n
250
- ***
251
- """
252
-
253
- systemprompt_template_3 = """
254
- ***
255
- 出力は以下のjsonテンプレートに従って出力してください。\n
256
- {
257
- "変更内容": "",
258
- "変更理由": "",
259
- "品質への影響": ""
260
- }
261
- ***
262
- 変更内容は以下のようなフォーマットで揃えてください。
263
- 変更の種類の部分は「〇〇の変更」という自然な文章になるように適宜書き換えてください。
264
- {変更の種類}の変更
265
- 変更前: \n
266
- 変更後:
267
- ***
268
- 変更内容が複数ある場合は以下のような例にしたがってで揃えてください。
269
- 番号のすぐ後には変更の種類を添えてください。
270
- 充填機をAからBに、個箱の材質をXからYに変更する場合
271
-
272
- 1. 充填設備の変更
273
- 変更前: A
274
- 変更後: B
275
-
276
- 2. 個箱の材質の変更
277
- 変更前: X
278
- 変更後: Y
279
- ***
280
- 変更理由と品質への影響も、変更の種類によって
281
-
282
- 1. 充填設備の変更
283
-
284
- 2. 包装設備の変更
285
-
286
- とナンバリングしてください。変更が一つだけの場合はナンバリングする必要はありません
287
- ***
288
- 品質への影響は、3問目以降の回答ををまとめる形で出力してください。決して漏れがないようにしてください。
289
- ***
290
- 「です・ます」調ではなく「だ・である」調で出力してください。\n
291
- """
292
-
293
- # 変更の種類・内容に基づいて確認事項の質問を作成
294
- q = []
295
-
296
- for change in range(change_num):
297
- q.append(f"Q{change+1}-1 : 今回の変更内容を教えて下さい。変更前と変更後の両方を明記してください。")
298
- q.append(f"Q{change+1}-2 : 今回の変更を行う理由を教えて下さい。")
299
-
300
- qs = question(types[change], subtypes[change], subtypes2[change])
301
- qs_num = len(qs)
302
- for i in range(qs_num):
303
- q.append(f"Q{change+1}-{i+3} : {qs[i]}")
304
-
305
-
306
- q.append(f"Q{change+1}-{qs_num+3} : その他、変更による品質への影響を考慮すべき点はありますか?考慮すべき点があれば、その理由とともに説明してください。")
307
-
308
- # unique = {}
309
- # for question, item in q:
310
- # if item not in unique:
311
- # unique[item] = question
312
- # q = list(unique.values())
313
-
314
- question_num = len(q)
315
-
316
- # # 質問をナンバリング
317
- # qq = [None]*question_num
318
- # for change in range(question_num):
319
- # qq[change] = f"Q{change+1} : {q[change]}"
320
-
321
-
322
-
323
- # 質問に回答させる
324
- with st.form(key='my_form2_1'):
325
- input_values = [None] * question_num
326
- nos = [None] * question_num
327
- qs_now = 1
328
- st.markdown(f"**1つ目の変更について**")
329
- for k in range(question_num):
330
- if int(q[k][1]) != qs_now:
331
- st.markdown(f"**{qs_now+1}つ目の変更について**")
332
- qs_now += 1
333
- input_values[k] = st.text_area(q[k], key=f'input_1_{k}')
334
- nos[k] = st.checkbox(f"特になし", key = f"check_{k}")
335
- st.write("\n")
336
- st.markdown("***")
337
- supplementary_information = st.text_area(f"Q{change_num+1} : 備考があれば記入してください。", key=f'supplementary_information_1')
338
- attached_files = st.text_area(f"Q{change_num+2} : 添付資料名を記入してください。", key=f'attached_files_1')
339
- submitted = st.form_submit_button('送信')
340
-
341
- if submitted:
342
- # 特になしの場合
343
- for k in range(question_num):
344
- if nos[k]:
345
- input_values[k] = "特になし"
346
-
347
- # システムプロンプトを作成する
348
- system_prompt = systemprompt_template_1
349
- system_prompt += systemprompt_template_2
350
- for k in range(question_num):
351
- system_prompt += q[k]
352
- system_prompt += "\n"
353
- system_prompt += systemprompt_template_3
354
-
355
-
356
- # ユーザープロンプトを作成する
357
- user_prompt = ""
358
- for k in range(question_num):
359
- user_prompt += f" A{q[k][1:4]} : {input_values[k]}"
360
- user_prompt += "\n"
361
-
362
- # OpenAIのAPIを使用して応答を生成
363
- client = OpenAI(api_key=open_api_key)
364
- response = client.chat.completions.create(
365
- model="gpt-4-0125-preview",
366
- messages=[
367
- {"role": "system", "content": system_prompt},
368
- {"role": "user", "content": user_prompt}
369
- ],
370
- response_format={ "type": "json_object" },
371
- temperature=0.0
372
- )
373
- response_text = response.choices[0].message.content
374
-
375
-
376
- # 応答をjsonに変換
377
-
378
- response_data = json.loads(response_text)
379
-
380
- # 申請日をdate形式から文字列(〇〇〇〇年〇月〇日)に変換
381
- application_date = application_date.isoformat()
382
- date_obj = datetime.strptime(application_date, "%Y-%m-%d")
383
- application_date = date_obj.strftime("%Y年%m月%d日")
384
-
385
- # jsonデータを作成
386
- json_data = {
387
- "工場": plant_type,
388
- "申請者": applicant_name,
389
- "申請日": application_date,
390
- "変更対象": change_details,
391
- "実施予定時期": implementation_date,
392
- "変更内容": str(response_data["変更内容"]),
393
- "変更理由": str(response_data["変更理由"]),
394
- "品質への影響": str(response_data["品質への影響"]),
395
- "添付資料": attached_files,
396
- "備考": supplementary_information,
397
- "ユーザープロンプト": user_prompt
398
- }
399
-
400
- # jsonファイルを作成
401
- filename = 'data/change_request.json'
402
- with open(filename, "w") as file:
403
- json.dump(json_data, file, ensure_ascii=False, indent=4)
404
-
405
- # jsonからdocxファイルを作成
406
- embed_output.main()
407
- st.session_state['document_generated'] = True
408
-
409
- # ⑤応答の結果を表示・ダウンロード・レビュー
410
- if st.session_state['document_generated']:
411
- st.markdown("以下の内容を記入した変更申請書(.docx)を作成しました。")
412
-
413
- # Wordファイルのダウンロードボタン
414
- with open("data/output.docx", "rb") as file:
415
- st.download_button(
416
- label="ダウンロード",
417
- data=file,
418
- file_name="output.docx",
419
- mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
420
- )
421
-
422
- # 各項目の内容を表示
423
- with open('data/change_request.json', 'r') as file:
424
- json_data = json.load(file)
425
-
426
- st.markdown(f'**申請者** : {json_data["申請者"]}')
427
- st.markdown(f'**申請日** : {json_data["申請日"]}')
428
- st.markdown(f'**変更対象** : {json_data["変更対象"]}')
429
- st.markdown(f'**実施予定時期** : {json_data["実施予定時期"]}')
430
- st.markdown(f'**変更内容** : {json_data["変更内容"]}')
431
- st.markdown(f'**変更理由** : {json_data["変更理由"]}')
432
- st.markdown(f'**品質への影響** : {json_data["品質への影響"]}')
433
- st.markdown(f'**添付資料** : {json_data["添付資料"]}')
434
- st.markdown(f'**備考** : {json_data["備考"]}')
435
-
436
- # # レビュー用のボタン
437
- review_button = st.button("レビューする")
438
- if review_button:
439
- query_content = json_data["変更内容"]
440
- query_reason = json_data["変更理由"]
441
- query_vec = get_embedding(query_content, client=client)
442
-
443
- # 名寄せ
444
- genres = [None] * change_num
445
- for i in range(change_num):
446
- match subtypes[i]:
447
- case "充填設備の変更":
448
- genres[i] = "充填機変更"
449
- case "製造メーカー(A社→B社)":
450
- genres[i] = "原料メーカー変更"
451
- case "仕込量変更(スケールアップ・ダウン)":
452
- genres[i] = "製造スケール変更"
453
- print(genres[i])
454
-
455
-
456
- res = index.query(
457
- vector=query_vec,
458
- filter={
459
- "genre": {"$in": genres}
460
- },
461
- top_k=3,
462
- include_metadata=True,
463
- )
464
- reasons = [r["metadata"]["change_reason"] for r in res["matches"]]
465
- print(f"reasons: {reasons}")
466
-
467
- # GPTにレビューを依頼
468
- system = f"""
469
- あなたは製薬会社の品質管理部門のリーダーです。今回、あなたの部下が作成した変更申請書の内容をレビューすることになりました。
470
- ## レビュー例 に示される変更理由の手本に基づいて,以下の'''変更理由'''の修正すべき点を指摘してください.
471
- ## レビュー例 は正しい変更理由を示しています.その内容を参考にして,以下の'''変更理由'''の修正すべき点を指摘してください.
472
- ただし改善例を示す必要はありません.
473
-
474
- それではステップバイステップで生成していきましょう。
475
- This is very important to my career.
476
-
477
- ## 出力例:
478
- - 例1:
479
- ユーザー:
480
- 前提知識 :
481
- '充填工程で、現行のノルデンチューブ充填機にJM2チューブ充填機を追加する。
482
- 変更前:ノルデンチューブ充填機(J-CH5)
483
- ノルデンチューブ充填機(J-CH8)
484
- 変更後:ノルデンチューブ充填機(J-CH5)
485
- ノルデンチューブ充填機(J-CH8)
486
- JM2チューブ充填機(J-CH4)
487
- '
488
- 変更理由 :
489
- 'ノルデンチューブ充填機(J-CH5)が故障した為、生産スケジュールの都合上、生産できる充填機を追加する。 '
490
- 返答:
491
- '
492
- - 故障した充填機について、機番を明記するとともに、『故障』ではなく『不具合』と表現を変更してください。また、部品の取り寄せを依頼している状況や、海外製であるため修理の目処が立たないことも追記が必要です。
493
- - 代替として使用するJM2チューブ充填機についても、機番を明記し、ジルダザック軟膏3%10gと同サイズのチューブホルダーがあることを説明してください。
494
- - さらに、JM2チューブ充填機が国内メーカーであるガデリウスの製品であり、同社が販売と修理を行っていることも追記してください。
495
-
496
-
497
- - 例2:
498
- ユーザー:
499
- 前提知識 :
500
- '
501
- 【原料:カルボキシビニルポリマーの変更】
502
- 変更前:メーカー;住友精化株式会社(岡山大鵬薬品支給)
503
- 商品名;HV-505HC
504
- 変更後:メーカー;富士フイルム和光純薬株式会社(万協調達)
505
- 商品名;ハイビスワコー103
506
- '
507
- 変更理由 :
508
- '現行品のカルボキシビニルポリマー(商品名;HV-505HC)が終売となるため。 '
509
- 返答:
510
- '
511
- - 必要十分な情報が記載されています。修正の必要はありません。
512
- '
513
-
514
- - 例3:
515
- ユーザー:
516
- 前提知識 :
517
- '
518
- 製造設備及び仕込み量の変更
519
- 変更前;1t乳化機1t仕込み もしくは1t乳化機750kg仕込み
520
- 変更後;500kg乳化機400kg仕込み
521
- '
522
- 変更理由 :
523
- '日邦薬品工業の販売数量が減ったため、製造量を減らして対応するため。 '
524
- 返答:
525
- '
526
- - 会社名は正式な社名を使用するようにしてください。この場合、『日邦薬品工業株式会社』が適切です。
527
- - また、文章全体を簡潔にまとめるために、『製造量を減らして対応するため』という部分は削除しても問題ないでしょう。
528
- - さらに、日邦薬品工業株式会社の役割を明確にするために、『発売元である』という情報を追加してください。
529
- '
530
- """
531
-
532
- user = f"""
533
- ## レビュー例:
534
- {reasons}
535
-
536
- ## 前提知識:
537
- {query_content}
538
-
539
- ## 変更理由:
540
- {query_reason}
541
-
542
- 返答:
543
- """
544
-
545
- completion = client.chat.completions.create(
546
- model="gpt-4-0125-preview",
547
- messages=[
548
- {
549
- "role": "system",
550
- "content": system,
551
- },
552
- {
553
- "role": "user",
554
- "content": user,
555
- },
556
- ],
557
- temperature=0.7,
558
- )
559
- review = completion.choices[0].message.content
560
-
561
- st.markdown(f"**レビュー結果** : {review}")
562
-
563
-
564
-
565
- # def deviation_report():
566
- # # タイトル
567
- # st.title("���脱発生報告書 文書生成")
568
-
569
- # # 環境変数からAPIキーを取得
570
- # load_dotenv()
571
- # open_api_key = os.environ.get("OPENAI_API_KEY")
572
-
573
- # # セッション状態の初期化
574
- # for state in ['form_submitted', 'document_generated']:
575
- # if state not in st.session_state:
576
- # st.session_state[state] = False
577
-
578
- # # ①報告者などの入力フォーム
579
- # with st.form("my_form_deviatoin"):
580
- # col1, col2 = st.columns(2)
581
- # with col1:
582
- # reporter_name = st.text_input("報告者")
583
- # with col2:
584
- # report_date = st.date_input("報告日", date.today())
585
-
586
- # col2_1, col2_2 = st.columns(2)
587
- # with col2_1:
588
- # product_name = st.text_input("品名")
589
- # with col2_2:
590
- # lot_number = st.text_input("ロットNo.")
591
-
592
- # col1, col2, col3 = st.columns([1, 1, 2])
593
- # with col1:
594
- # occurrence_date = st.date_input("発生日", date.today())
595
- # with col2:
596
- # occurrence_time = st.time_input("発生時間", datetime.now().time())
597
- # with col3:
598
- # occurrence_place = st.text_input("発生場所")
599
-
600
- # col2_1, col2_2 = st.columns(2)
601
- # with col2_1:
602
- # discoverer_name = st.text_input("発見者")
603
- # with col2_2:
604
- # worker_name = st.text_input("作業者")
605
-
606
- # submit_button0 = st.form_submit_button("進む")
607
- # if submit_button0:
608
- # st.session_state['form_submitted'] = True
609
-
610
- # # ②質問の回答からLLMの応答を生成
611
- # if st.session_state['form_submitted']:
612
- # # 質問リストを作成
613
- # q = ["Q1 : 逸脱内容を記入してください(どの手順・規格から逸脱したのか)",
614
- # "Q2 : 逸脱の発見の経緯を記入してください",
615
- # "Q3 : 応急処置とその処置の理由を記入してください",
616
- # "Q4 : 当該ロットの試験実施等による品質評価について記入してください",
617
- # "Q5 : 安定性モニタリングの対象ロットですか、もしくは対象ロットとしますか",
618
- # "Q6 : 安定性モニタリングの検体採取表の更新について記入してください",
619
- # "Q7 : 在庫品の原料・充填資材・包装資材に関して、在庫ロットの品質評価や在庫量・発注状況確認について記入してください",
620
- # "Q8 : 原料メーカー・資材メーカー・設備メーカーへの調査依頼について記入してください",
621
- # "Q9 : 次回製造ロットの生産スケジュール、受注状況の変更について記入してください",
622
- # "Q10 : 出荷日の変更について記入してください",
623
- # "Q11 : 製造販売業者への連絡について記入してください",
624
- # "Q12 : 他製品・他ラインへの影響について記入してください",
625
- # "Q13 : 過去製造ロットの品質の再評価について記入してください",
626
- # "Q14 : その他の品質への影響があれば記入してください"]
627
-
628
- # q_not = ["",
629
- # "",
630
- # "",
631
- # "品質評価は必要ない",
632
- # "対象ロットではない・しない",
633
- # "更新しない",
634
- # "評価・確認は必要ない",
635
- # "依頼は必要ない",
636
- # "変更しない",
637
- # "変更しない",
638
- # "連絡は必要ない",
639
- # "影響はない",
640
- # "再評価は必要ない",
641
- # "なし"]
642
- # q_num = len(q)
643
-
644
- # with st.form(key='my_form2_deviation'):
645
- # input_values = [None] * q_num
646
- # nos = [None] * q_num
647
- # for k in range(q_num):
648
- # input_values[k] = st.text_area(q[k], key=f'input_deviatoin_{k}')
649
- # if (k > 2):
650
- # nos[k] = st.checkbox(f"{q_not[k]}", key = f"check_{k}")
651
- # st.markdown("***")
652
-
653
-
654
- # attached_files = st.text_area(f"Q14 : 添付資料名を記入してください。", key=f'attached_files_deviation')
655
- # no_attached_file = st.checkbox("添付資料なし", key = "check_attached_file")
656
- # supplementary_information = st.text_area(f"Q15 : 備考があれば記入してください。", key=f'supplementary_information_deviation')
657
- # no_supplementary_information = st.checkbox("なし", key = "check_supplementary_information")
658
- # submitted = st.form_submit_button('送信')
659
-
660
- # if submitted:
661
- # # 無しにチェックがつけられた場合
662
- # for i in range(q_num-3):
663
- # if nos[i+2]:
664
- # input_values[i+3] = q_not[i+3]
665
-
666
- # if no_attached_file:
667
- # attached_files = "x"
668
- # if no_supplementary_information:
669
- # supplementary_information = "なし"
670
-
671
- # # システムプロンプトを作成する
672
- # systemprompt_template_1 = """
673
- # ***
674
- # 逸脱内容・発見の経緯:
675
- # 応急処置・処置の理由:
676
- # 品質への影響の調査状況:
677
- # ***
678
- # あなたは、質問に対して入力された情報から、詳しい内容を含んだ逸脱発生報告書の文章を生成するアシスタントです。\n
679
- # 上記の形式に従って、入力された情報を当てはめて出力してください。報告書の読みやすさ、明瞭さ、正確さに注意してください。\n
680
- # 変更や必要がないという回答に関してはは特にそのことを記述する必要はありません。特別な記述があった場合にのみ記述してください。
681
- # 「だ・である」調で出力してください。\n
682
- # あなたは返答をすべてJSON形式で出力します。\n
683
- # あなたが質問した内容は以下です。\n
684
- # ***
685
- # """
686
-
687
- # systemprompt_template_2 = """
688
- # ***
689
- # 出力は以下のjsonテンプレートに従って出力してください。\n
690
- # {
691
- # "逸脱内容・発見の経緯":"",
692
- # "応急処置・処置の理由":"",
693
- # "品質への影響の調査状況": "",
694
- # }
695
- # ***
696
- # 「です・ます」調ではなく「だ・である」調で出力してください。\n
697
- # """
698
-
699
- # system_prompt = systemprompt_template_1
700
- # for k in range(q_num):
701
- # system_prompt += q[k]
702
- # system_prompt += "\n"
703
- # system_prompt += systemprompt_template_2
704
-
705
- # # ユーザープロンプトを作成する
706
- # user_prompt = ""
707
- # for k in range(q_num):
708
- # user_prompt += f" A{k+1} : {input_values[k]}"
709
- # user_prompt += "\n"
710
-
711
- # # OpenAIのAPIを使用して応答を生成
712
- # client = OpenAI(api_key=open_api_key)
713
- # response = client.chat.completions.create(
714
- # model="gpt-4-0125-preview",
715
- # messages=[
716
- # {"role": "system", "content": system_prompt},
717
- # {"role": "user", "content": user_prompt}
718
- # ],
719
- # response_format={ "type": "json_object" },
720
- # temperature=0.0
721
- # )
722
- # response_text = response.choices[0].message.content
723
-
724
- # # 応答をjsonに変換
725
- # response_data = json.loads(response_text)
726
-
727
-
728
- # # # 申請日をdate形式から文字列(〇〇〇〇年〇月〇日)に変換
729
- # # application_date = application_date.isoformat()
730
- # # date_obj = datetime.strptime(application_date, "%Y-%m-%d")
731
- # # application_date = date_obj.strftime("%Y年%m月%d日")
732
- # report_date = report_date.strftime("%Y年%m月%d日")
733
- # occurrence_datetime = datetime.combine(occurrence_date, occurrence_time).strftime("%Y年%m月%d日%H時%M分")
734
-
735
- # # jsonデータを作成
736
- # json_data = {
737
- # "報告者": reporter_name,
738
- # "報告日": report_date,
739
- # "品名": product_name,
740
- # "ロットNo.": lot_number,
741
- # "発生日時": occurrence_datetime,
742
- # "発生場所": occurrence_place,
743
- # "発見者": discoverer_name,
744
- # "作業者": worker_name,
745
- # "逸脱内容・発見の経緯": str(response_data["逸脱内容・発見の経緯"]),
746
- # "応急処置・処置の理由": str(response_data["応急処置・処置の理由"]),
747
- # "品質への影響の調査状況": str(response_data["品質への影響の調査状況"]),
748
- # "添付資料": attached_files,
749
- # "備考": supplementary_information,
750
- # "ユーザープロンプト": user_prompt
751
- # }
752
-
753
- # # jsonファイルを作成
754
- # filename = 'data/deviation_report.json'
755
- # with open(filename, "w") as file:
756
- # json.dump(json_data, file, ensure_ascii=False, indent=4)
757
-
758
- # # jsonからdocxファイルを作成
759
- # embed_output_deviation.main()
760
- # st.session_state['document_generated'] = True
761
-
762
- # # ③応答の結果を表示・ダウンロード・レビュー
763
- # if st.session_state['document_generated']:
764
- # st.markdown("以下の内容を記入した変更申請書(.docx)を作成しました。")
765
-
766
- # # Wordファイルのダウンロードボタン
767
- # with open("data/output_deviation.docx", "rb") as file:
768
- # st.download_button(
769
- # label="ダウンロード",
770
- # data=file,
771
- # file_name="output_deviation.docx",
772
- # mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
773
- # )
774
-
775
- # # 各項目の内容を表示
776
- # with open('data/deviation_report.json', 'r') as file:
777
- # json_data = json.load(file)
778
- # st.markdown(f'**報告者** : {json_data["報告者"]}')
779
- # st.markdown(f'**報告日** : {json_data["報告日"]}')
780
- # st.markdown(f'**品名** : {json_data["品名"]}')
781
- # st.markdown(f'**ロットNo.** : {json_data["ロットNo."]}')
782
- # st.markdown(f'**発生日時** : {json_data["発生日時"]}')
783
- # st.markdown(f'**発生場所** : {json_data["発生場所"]}')
784
- # st.markdown(f'**発見者** : {json_data["発見者"]}')
785
- # st.markdown(f'**作業者** : {json_data["作業者"]}')
786
- # st.markdown(f'**逸脱内容・発見の経緯** : {json_data["逸脱内容・発見の経緯"]}')
787
- # st.markdown(f'**応急処置・処置の理由** : {json_data["応急処置・処置の理由"]}')
788
- # st.markdown(f'**品質への影響の調査状況** : {json_data["品質への影響の調査状況"]}')
789
- # st.markdown(f'**添付資料** : {json_data["添付資料"]}')
790
- # st.markdown(f'**備考** : {json_data["備考"]}')
791
-
792
-
793
-
794
- pages = {
795
- "変更申請書": change_request
796
- # "逸脱発生報告書": deviation_report
797
- }
798
- selected_page = st.sidebar.selectbox("作成する文書を選択してください。", list(pages.keys()))
799
- pages[selected_page]()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{src → archive}/pinecone.ipynb RENAMED
File without changes
data/change_request.json CHANGED
@@ -1,11 +1,13 @@
1
  {
2
- "申請者": "test",
3
- "申請日": "2024-03-05",
4
- "変更対象": "原薬",
5
- "実施予定時期": "",
6
- "変更内容": "詳細は提供されておりません。",
7
- "変更理由": "生産スケジュールの調整を容易とするため。",
8
- "品質への影響": "詳細は提供されておりません。",
9
- "備考": "詳しくは提供されていない項目が多いです。全ての項目について必要な情報が提供されていない場合、適切な評価が難しい可能性があることを留意してください。",
10
- "ユーザープロンプト": "A 1 : あ\n\nA 2 : 生産スケジュールの調整を容易とするため。\n\nA 3 : あ\n\nA 4 : あ\n\nA 5 : あ\n\nA 6 : あ\n\nA 7 : あ\n\nA 8 : あ\n\nA 9 : あ\n\n"
 
 
11
  }
 
1
  {
2
+ "�H��": "���E���H��",
3
+ "�\����": "",
4
+ "�\����": "2024�N05��28��",
5
+ "�ύX�Ώ�": "",
6
+ "���{�\�莞��": "",
7
+ "�ύX���e": "��񂪕s�����Ă��邽�߁A��̓I�ȕύX���e�����ł��Ȃ��B",
8
+ "�ύX���R": "��񂪕s�����Ă��邽�߁A�ύX���R�����ł��Ȃ��B",
9
+ "�i���ւ̉e��": "��񂪕s�����Ă��邽�߁A�i���ւ̉e����]���ł��Ȃ��B",
10
+ "�Y�t����": "",
11
+ "���l": "",
12
+ "���[�U�[�v�����v�g": " A1-1 : ������\n A1-2 : \n A1-3 : \n A1-4 : \n A1-5 : \n A1-6 : \n A1-7 : \n A1-8 : \n A1-9 : \n A1-1 : \n A1-1 : \n"
13
  }
data/output.docx CHANGED
Binary files a/data/output.docx and b/data/output.docx differ
 
main.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ from src.embed_output import main as embed_output
4
+ from src.forms import change_request_form
5
+
6
+ pages = {
7
+ "変更申請書": change_request_form,
8
+ # "逸脱発生報告書": deviation_report_form
9
+ }
10
+
11
+ selected_page = st.sidebar.selectbox(
12
+ "作成する文書を選択してください。", list(pages.keys())
13
+ )
14
+ pages[selected_page](embed_output)
src/embed_output_deviation.py CHANGED
@@ -1,6 +1,7 @@
1
  from docx import Document
2
  import json
3
 
 
4
  def replace_text_in_paragraphs(paragraphs, replacements):
5
  """
6
  パラグラフ内のテキストを置き換える
@@ -35,10 +36,9 @@ def main():
35
  # ファイルパスと置き換えるテキストの辞書
36
  # ここをJSONから読み込むように変更する
37
 
38
-
39
  template_path = "data/template_deviation.docx"
40
 
41
- with open('data/deviation_report.json', 'r') as file:
42
  data = json.load(file)
43
 
44
  if data["添付資料"] == "x":
@@ -46,7 +46,7 @@ def main():
46
  no = "☑"
47
  attached_file = ""
48
  else:
49
- yes = "☑"
50
  no = "□"
51
  attached_file = data["添付資料"]
52
 
@@ -64,8 +64,8 @@ def main():
64
  "[impact_on_quality]": data["品質への影響の調査状況"],
65
  "[yes]": yes,
66
  "[no]": no,
67
- "[attached_file]" : attached_file,
68
- "[supplementary_information]": data["備考"]
69
  }
70
 
71
  doc = replace_text_in_docx(template_path, replacements)
@@ -75,4 +75,4 @@ def main():
75
 
76
 
77
  if __name__ == "__main__":
78
- main()
 
1
  from docx import Document
2
  import json
3
 
4
+
5
  def replace_text_in_paragraphs(paragraphs, replacements):
6
  """
7
  パラグラフ内のテキストを置き換える
 
36
  # ファイルパスと置き換えるテキストの辞書
37
  # ここをJSONから読み込むように変更する
38
 
 
39
  template_path = "data/template_deviation.docx"
40
 
41
+ with open("data/deviation_report.json", "r") as file:
42
  data = json.load(file)
43
 
44
  if data["添付資料"] == "x":
 
46
  no = "☑"
47
  attached_file = ""
48
  else:
49
+ yes = "☑"
50
  no = "□"
51
  attached_file = data["添付資料"]
52
 
 
64
  "[impact_on_quality]": data["品質への影響の調査状況"],
65
  "[yes]": yes,
66
  "[no]": no,
67
+ "[attached_file]": attached_file,
68
+ "[supplementary_information]": data["備考"],
69
  }
70
 
71
  doc = replace_text_in_docx(template_path, replacements)
 
75
 
76
 
77
  if __name__ == "__main__":
78
+ main()
src/forms.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import date, datetime
4
+
5
+ import streamlit as st
6
+ from openai import OpenAI
7
+ from pinecone import Pinecone
8
+
9
+ from src.prompts import (
10
+ system_template_review,
11
+ systemprompt_template_1,
12
+ systemprompt_template_2,
13
+ systemprompt_template_3,
14
+ user_template_review,
15
+ )
16
+ from src.questions import get_questions
17
+ from src.utils import get_embedding, load_env_vars
18
+
19
+
20
+ def change_request_form(embed_output):
21
+ st.title("変更申請書 文書生成")
22
+
23
+ load_env_vars()
24
+ open_api_key = os.environ.get("OPENAI_API_KEY")
25
+ pc = Pinecone(os.environ["PINECONE_API_KEY"])
26
+ index = pc.Index("bankyo")
27
+
28
+ client = OpenAI(api_key=open_api_key)
29
+
30
+ with st.form("my_form"):
31
+ col1, col2 = st.columns(2)
32
+ with col1:
33
+ plant_type = st.selectbox(
34
+ "工場", ["", "第一・第二工場", "第三工場", "第四工場"]
35
+ )
36
+ with col2:
37
+ applicant_name = st.text_input("申請者")
38
+
39
+ col2_1, col2_2 = st.columns(2)
40
+ with col2_1:
41
+ change_num = st.selectbox("変更の件数", [str(i) for i in range(1, 11)])
42
+ with col2_2:
43
+ application_date = st.date_input("申請日", date.today())
44
+
45
+ change_details = st.text_area(
46
+ "変更対象 (変更対象となる製品名・原料名・設備名等を記入する)"
47
+ )
48
+ implementation_date = st.text_area("実施予定時期")
49
+
50
+ submit_button0 = st.form_submit_button("進む")
51
+ if submit_button0:
52
+ st.session_state["form_submitted"] = True
53
+
54
+ change_num = int(change_num)
55
+
56
+ for state in ["form_submitted", "document_generated"]:
57
+ if state not in st.session_state:
58
+ st.session_state[state] = False
59
+ for change in range(change_num):
60
+ if f"type_chosen_{change}" not in st.session_state:
61
+ st.session_state[f"type_chosen_{change}"] = False
62
+ if f"subtype_chosen_{change}" not in st.session_state:
63
+ st.session_state[f"subtype_chosen_{change}"] = False
64
+
65
+ types = [None] * change_num
66
+ subtypes = [None] * change_num
67
+ subtypes2 = [None] * change_num
68
+
69
+ if st.session_state["form_submitted"]:
70
+ st.session_state[f"subtype_chosen_-1"] = True
71
+
72
+ for change in range(change_num):
73
+ if st.session_state[f"subtype_chosen_{change-1}"]:
74
+ if change_num != 1:
75
+ st.markdown(f"**{change+1}つ目の変更**")
76
+ types[change] = st.selectbox(
77
+ "変更の種類を選択してください:",
78
+ [
79
+ "",
80
+ "原薬",
81
+ "添加物",
82
+ "資材変更",
83
+ "デザイン変更",
84
+ "製造方法・製造設備",
85
+ "製造支援システム",
86
+ "構造設備・設備移設",
87
+ "品質管理",
88
+ "出荷・物流",
89
+ ],
90
+ key=f"type_{change}",
91
+ )
92
+ if types[change]:
93
+ st.session_state[f"type_chosen_{change}"] = True
94
+
95
+ if st.session_state[f"type_chosen_{change}"]:
96
+ if types[change] == "原薬":
97
+ subtypes2[change] = "x"
98
+ subtypes[change] = st.selectbox(
99
+ "変更内容を選択してください:",
100
+ [
101
+ "",
102
+ "グレード",
103
+ "製造設備",
104
+ "製造方法",
105
+ "製造所(A社→B社)",
106
+ "製造場所(製造メーカーに変更はない。A社の①工場→A社の②工場)",
107
+ ],
108
+ key=f"subtype_1_{change}",
109
+ )
110
+ elif types[change] == "添加物":
111
+ subtypes2[change] = "x"
112
+ subtypes[change] = st.selectbox(
113
+ "変更内容を選択してください:",
114
+ [
115
+ "",
116
+ "グレード(型番)",
117
+ "製造設備",
118
+ "製造メーカー(A社→B社)",
119
+ "製造場所(製造メーカーに変更はない。A社の①工場→A社の②工場)",
120
+ ],
121
+ key=f"subtype_2_{change}",
122
+ )
123
+ elif types[change] == "資材変更":
124
+ subtypes[change] = st.selectbox(
125
+ "変更内容を選択してください:",
126
+ [
127
+ "",
128
+ "チューブ・容器キャップ(直接容器)",
129
+ "容器ラベ���",
130
+ "添付文書",
131
+ "個箱",
132
+ "セット箱",
133
+ "シール(例:見本用シール、変更品シール)",
134
+ "パッキングケース",
135
+ "パッキングケースラベル",
136
+ ],
137
+ key=f"subtype_3_{change}",
138
+ )
139
+ elif types[change] == "デザイン変更":
140
+ subtypes2[change] = "x"
141
+ subtypes[change] = "x"
142
+ elif types[change] == "製造方法・製造設備":
143
+ subtypes2[change] = "x"
144
+ subtypes[change] = st.selectbox(
145
+ "変更内容を選択してください:",
146
+ [
147
+ "",
148
+ "仕込量変更(スケールアップ・ダウン)",
149
+ "標準的仕込量変更(100%仕込み→10●%仕込みに変更)",
150
+ "出荷規格の変更",
151
+ "調合設備の変更",
152
+ "充填設備の変更",
153
+ "包装設備の変更",
154
+ "調合工程のパラメーター変更",
155
+ "充填工程のパラメーター変更(例:充填速度、設定温度、規格範囲、トルク値)",
156
+ "包装工程のパラメーター変更",
157
+ "重量基準値の変更(ウエイトチェッカーでの計量)",
158
+ "包装工程 作業人数の変更",
159
+ "洗浄方法の変更",
160
+ ],
161
+ key=f"subtype_4_{change}",
162
+ )
163
+ elif types[change] == "製造支援システム":
164
+ subtypes2[change] = "x"
165
+ subtypes[change] = st.selectbox(
166
+ "変更内容を選択してください:",
167
+ ["", "製造支援システム(製造用水、空調システム)"],
168
+ key=f"subtype_5_{change}",
169
+ )
170
+ elif types[change] == "構造設備・設備移設":
171
+ subtypes2[change] = "x"
172
+ subtypes[change] = st.selectbox(
173
+ "変更内容を選択してください:",
174
+ ["", "構造設備", "設備の移設(製造設備・試験設備)"],
175
+ key=f"subtype_6_{change}",
176
+ )
177
+ elif types[change] == "品質管理":
178
+ subtypes2[change] = "x"
179
+ subtypes[change] = st.selectbox(
180
+ "変更内容を選択してください:",
181
+ [
182
+ "",
183
+ "試験方法の変更",
184
+ "試験室の変更",
185
+ "外部試験機関の変更",
186
+ "試験設備の変更・更新",
187
+ "バルク保管期間の変更",
188
+ ],
189
+ key=f"subtype_7_{change}",
190
+ )
191
+ elif types[change] == "出荷・物流":
192
+ subtypes2[change] = "x"
193
+ subtypes[change] = st.selectbox(
194
+ "変更内容を選択してください:",
195
+ ["", "製品納品先の変更"],
196
+ key=f"subtype_8_{change}",
197
+ )
198
+
199
+ if (
200
+ subtypes[change]
201
+ == "充填工程のパラメーター変更(例:充填速度、設定温度、規格範囲、トルク値)"
202
+ ):
203
+ subtypes2[change] = st.selectbox(
204
+ "詳しい内容を選択してください:",
205
+ ["", "液", "クリーム", "チューブ"],
206
+ key=f"subtype_9_{change}",
207
+ )
208
+
209
+ match types[change], subtypes[change]:
210
+ case "資材変更", "チューブ・容器キャップ(直接容器)":
211
+ subtypes2[change] = st.selectbox(
212
+ "詳しい内容を選択してください:",
213
+ [
214
+ "",
215
+ "製造設備の変更",
216
+ "製造所の変更",
217
+ "材質の変更",
218
+ "形状変更",
219
+ "版番号の変更",
220
+ ],
221
+ key=f"subtype_3_1_{change}",
222
+ )
223
+ case "資材変更", "容器ラベル":
224
+ subtypes2[change] = st.selectbox(
225
+ "詳しい内容を選択してください:",
226
+ [
227
+ "",
228
+ "版番号の変更",
229
+ "セルフメディケーション税制対応マークの追加",
230
+ ],
231
+ key=f"subtype_3_2_{change}",
232
+ )
233
+ case "資材変更", "添付文書":
234
+ subtypes2[change] = st.selectbox(
235
+ "詳しい内容を選択してください:",
236
+ [
237
+ "",
238
+ "製造所の変更",
239
+ "材質変更",
240
+ "厚さ変更",
241
+ "版番号の変更",
242
+ ],
243
+ key=f"subtype_3_3_{change}",
244
+ )
245
+ case "資材変更", "個箱":
246
+ subtypes2[change] = st.selectbox(
247
+ "詳しい内容を選択してください:",
248
+ [
249
+ "",
250
+ "製造所の変更",
251
+ "捺印位置の変更",
252
+ "個箱形状変更",
253
+ "個箱の材質(表面加工)",
254
+ "版番号の変更",
255
+ "セルフメディケーション税制対応マークの追加",
256
+ ],
257
+ key=f"subtype_3_4_{change}",
258
+ )
259
+ case "資材変更", "セット箱":
260
+ subtypes2[change] = st.selectbox(
261
+ "詳しい内容を選択してください:",
262
+ [
263
+ "",
264
+ "製造所の変更",
265
+ "捺印位置の変更",
266
+ "セット箱形状変更",
267
+ "版番号の変更",
268
+ ],
269
+ key=f"subtype_3_5_{change}",
270
+ )
271
+ case "資材変更", "シール(例:見本用シール、変更品シール)":
272
+ subtypes2[change] = st.selectbox(
273
+ "詳しい内容を選択してください:",
274
+ ["", "サイズの変更", "版番号の変更"],
275
+ key=f"subtype_3_6_{change}",
276
+ )
277
+ case "資材変更", "パッキングケース":
278
+ subtypes2[change] = st.selectbox(
279
+ "詳しい内容を選択してください:",
280
+ ["", "版番号の変更"],
281
+ key=f"subtype_3_7_{change}",
282
+ )
283
+ case "資材変更", "パッキングケースラベル":
284
+ subtypes2[change] = st.selectbox(
285
+ "詳しい内容を選択してください:",
286
+ ["", "版番号の変更"],
287
+ key=f"subtype_3_8_{change}",
288
+ )
289
+
290
+ if subtypes[change] and subtypes2[change]:
291
+ st.session_state[f"subtype_chosen_{change}"] = True
292
+
293
+ if st.session_state[f"subtype_chosen_{change_num-1}"]:
294
+ q = []
295
+
296
+ for change in range(change_num):
297
+ q.append(
298
+ f"Q{change+1}-1 : 今回の変更内容を教えて下さい。変更前と変更後の両方を明記してください。"
299
+ )
300
+ q.append(f"Q{change+1}-2 : 今回の変更を行う理由を教えて下さい。")
301
+
302
+ qs = get_questions(types[change], subtypes[change], subtypes2[change])
303
+ qs_num = len(qs)
304
+ for i in range(qs_num):
305
+ q.append(f"Q{change+1}-{i+3} : {qs[i]}")
306
+
307
+ q.append(
308
+ f"Q{change+1}-{qs_num+3} : その他、変更による品質への影響を考慮すべき点はありますか?考慮すべき点があれば、その理由とともに説明してください。"
309
+ )
310
+
311
+ question_num = len(q)
312
+
313
+ with st.form(key="my_form2_1"):
314
+ input_values = [None] * question_num
315
+ nos = [None] * question_num
316
+ qs_now = 1
317
+ st.markdown(f"**1つ目の変更について**")
318
+ for k in range(question_num):
319
+ if int(q[k][1]) != qs_now:
320
+ st.markdown(f"**{qs_now+1}つ目の変更について**")
321
+ qs_now += 1
322
+ input_values[k] = st.text_area(q[k], key=f"input_1_{k}")
323
+ nos[k] = st.checkbox(f"特になし", key=f"check_{k}")
324
+ st.write("\n")
325
+ st.markdown("***")
326
+ supplementary_information = st.text_area(
327
+ f"Q{change_num+1} : 備考があれば記入してください。",
328
+ key=f"supplementary_information_1",
329
+ )
330
+ attached_files = st.text_area(
331
+ f"Q{change_num+2} : 添付資料名を記入してください。",
332
+ key=f"attached_files_1",
333
+ )
334
+ submitted = st.form_submit_button("送信")
335
+
336
+ if submitted:
337
+ for k in range(question_num):
338
+ if nos[k]:
339
+ input_values[k] = "特になし"
340
+
341
+ system_prompt = systemprompt_template_1
342
+ system_prompt += systemprompt_template_2
343
+ for k in range(question_num):
344
+ system_prompt += q[k]
345
+ system_prompt += "\n"
346
+ system_prompt += systemprompt_template_3
347
+
348
+ user_prompt = ""
349
+ for k in range(question_num):
350
+ user_prompt += f" A{q[k][1:4]} : {input_values[k]}"
351
+ user_prompt += "\n"
352
+
353
+ client = OpenAI(api_key=open_api_key)
354
+ response = client.chat.completions.create(
355
+ model="gpt-4-0125-preview",
356
+ messages=[
357
+ {"role": "system", "content": system_prompt},
358
+ {"role": "user", "content": user_prompt},
359
+ ],
360
+ response_format={"type": "json_object"},
361
+ temperature=0.0,
362
+ )
363
+ response_text = response.choices[0].message.content
364
+ response_data = json.loads(response_text)
365
+
366
+ application_date = application_date.isoformat()
367
+ date_obj = datetime.strptime(application_date, "%Y-%m-%d")
368
+ application_date = date_obj.strftime("%Y年%m月%d日")
369
+
370
+ json_data = {
371
+ "工場": plant_type,
372
+ "申請者": applicant_name,
373
+ "申請日": application_date,
374
+ "変更対象": change_details,
375
+ "実施予定時期": implementation_date,
376
+ "変更内容": str(response_data["変更内容"]),
377
+ "変更理由": str(response_data["変更理由"]),
378
+ "品質への影響": str(response_data["品質への影響"]),
379
+ "添付資料": attached_files,
380
+ "備考": supplementary_information,
381
+ "ユーザープロンプト": user_prompt,
382
+ }
383
+
384
+ filename = "data/change_request.json"
385
+ with open(filename, "w") as file:
386
+ json.dump(json_data, file, ensure_ascii=False, indent=4)
387
+
388
+ embed_output()
389
+ st.session_state["document_generated"] = True
390
+
391
+ if st.session_state["document_generated"]:
392
+ st.markdown("以下の内容を記入した変更申請書(.docx)を作成しました。")
393
+
394
+ with open("data/output.docx", "rb") as file:
395
+ st.download_button(
396
+ label="ダウンロード",
397
+ data=file,
398
+ file_name="output.docx",
399
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
400
+ )
401
+
402
+ with open("data/change_request.json", "r") as file:
403
+ json_data = json.load(file)
404
+
405
+ st.markdown(f'**申請者** : {json_data["申請者"]}')
406
+ st.markdown(f'**申請日** : {json_data["申請日"]}')
407
+ st.markdown(f'**変更対象** : {json_data["変更対象"]}')
408
+ st.markdown(f'**実施予定時期** : {json_data["実施予定時期"]}')
409
+ st.markdown(f'**変更内容** : {json_data["変更内容"]}')
410
+ st.markdown(f'**変更理由** : {json_data["変更理由"]}')
411
+ st.markdown(f'**品質への影響** : {json_data["品質への影響"]}')
412
+ st.markdown(f'**添付資料** : {json_data["添付資料"]}')
413
+ st.markdown(f'**備考** : {json_data["備考"]}')
414
+
415
+ review_button = st.button("レビューする")
416
+ if review_button:
417
+ query_content = json_data["変更内容"]
418
+ query_reason = json_data["変更理由"]
419
+ query_vec = get_embedding(query_content, client=client)
420
+
421
+ genres = [None] * change_num
422
+ for i in range(change_num):
423
+ match subtypes[i]:
424
+ case "充填設備の変更":
425
+ genres[i] = "充填機変更"
426
+ case "製造メーカー(A社→B社)":
427
+ genres[i] = "原料メーカー変更"
428
+ case "仕込量変更(スケールアップ・ダウン)":
429
+ genres[i] = "製造スケール変更"
430
+
431
+ res = index.query(
432
+ vector=query_vec,
433
+ filter={"genre": {"$in": genres}},
434
+ top_k=3,
435
+ include_metadata=True,
436
+ )
437
+ reasons = [r["metadata"]["change_reason"] for r in res["matches"]]
438
+
439
+ system = system_template_review
440
+
441
+ user = user_template_review.format(
442
+ reasons=reasons, query_content=query_content, query_reason=query_reason
443
+ )
444
+
445
+ completion = client.chat.completions.create(
446
+ model="gpt-4o",
447
+ messages=[
448
+ {"role": "system", "content": system},
449
+ {"role": "user", "content": user},
450
+ ],
451
+ temperature=0.7,
452
+ )
453
+ review = completion.choices[0].message.content
454
+ st.markdown(f"**レビュー結果** : {review}")
src/prompts.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ systemprompt_template_1 = """
2
+ ***
3
+ 変更内容:
4
+ 変更理由:
5
+ 品質への影響:
6
+ ***
7
+ あなたは、質問に対して入力された情報から、詳しい内容を含んだ申請書の文章を生成するアシスタントです。
8
+ 上記の形式に従って、入力された情報を当てはめて出力してください。漏れがないようにしてください。変更がないことに関しては、まとめず主語を明確にして「品質への影響」で言及してください。
9
+ 「だ・である」調で出力してください。
10
+ あなたは返答をすべてJSON形式で出力します。
11
+ ***
12
+ """
13
+
14
+ systemprompt_template_2 = """
15
+ 今回の変更の種類は以下です。
16
+ """
17
+
18
+ systemprompt_template_3 = """
19
+ ***
20
+ 出力は以下のjsonテンプレートに従って出力してください。
21
+ {
22
+ "変更内容": "",
23
+ "変更理由": "",
24
+ "品質への影響": ""
25
+ }
26
+ ***
27
+ 変更内容は以下のようなフォーマットで揃えてください。
28
+ 変更の種類の部分は「〇〇の変更」という自然な文章になるように適宜書き換えてください。
29
+ {変更の種類}の変更
30
+ 変更前:
31
+ 変更後:
32
+ ***
33
+ 変更内容が複数ある場合は以下のような例にしたがってで揃えてください。
34
+ 番号のすぐ後には変更の種類を添えてください。
35
+ 充填機をAからBに、個箱の材質をXからYに変更する場合
36
+
37
+ 1. 充填設備の変更
38
+ 変更前: A
39
+ 変更後: B
40
+
41
+ 2. 個箱の材質の変更
42
+ 変更前: X
43
+ 変更後: Y
44
+ ***
45
+ 変更理由と品質への影響も、変更の種類によって
46
+
47
+ 1. 充填設備の変更
48
+
49
+ 2. 包装設備の変更
50
+
51
+ とナンバリングしてください。変更が一つだけの場合はナンバリングする必要はありません
52
+ ***
53
+ 品質への影響は、3問目以降の回答をまとめる形で出力してください。決して漏れがないようにしてください。
54
+ ***
55
+ 「です・ます」調ではなく「だ・である」調で出力してください。
56
+ ***
57
+ """
58
+
59
+ system_template_review = """
60
+ あなたは製薬会社の品質管理部門のリーダーです。今回、あなたの部下が作成した変更申請書の内容をレビューすることになりました。
61
+ ## レビュー例 に示される変更理由の手本に基づいて,以下の'''変更理由'''の修正すべき点を指摘してください.
62
+ ## レビュー例 は正しい変更理由を示しています.その内容を参考にして,以下の'''変更理由'''の修正すべき点を指摘してください.
63
+ ただし改善例を示す必要はありません.
64
+
65
+ それではステップバイステップで生成していきましょう。
66
+ This is very important to my career.
67
+
68
+ ## 出力例:
69
+ - 例1:
70
+ ユーザー:
71
+ 前提知識 :
72
+ '充填工程で、現行のノルデンチューブ充填機にJM2チューブ充填機を追加する。
73
+ 変更前:ノルデンチューブ充填機(J-CH5)
74
+ ノルデンチューブ充填機(J-CH8)
75
+ 変更後:ノルデンチューブ充填機(J-CH5)
76
+ ノルデンチューブ充填機(J-CH8)
77
+ JM2チューブ充填機(J-CH4)
78
+ '
79
+ 変更理由 :
80
+ 'ノルデンチューブ充填機(J-CH5)が故障した為、生産スケジュールの都合上、生産できる充填機を追加する。 '
81
+ 返答:
82
+ '
83
+ - 故障した充填機について、機番を明記するとともに、『故障』ではなく『不具合』と表現を変更してください。また、部品の取り寄せを依頼している状況や、海外製であるため修理の目処が立たないことも追記が必要です。
84
+ - 代替として使用するJM2チューブ充填機についても、機番を明記し、ジルダザック軟膏3%10gと同サイズのチューブホルダーがあることを説明してください。
85
+ - さらに、JM2チューブ充填機が国内メーカーであるガデリウスの製品であり、同社が販売と修理を行っていることも追記してください。
86
+ '
87
+
88
+ - 例2:
89
+ ユーザー:
90
+ 前提知識 :
91
+ '
92
+ 【原料:カルボキシビニルポリマーの変更】
93
+ 変更前:メーカー;住友精化株式会社(岡山大鵬薬品支給)
94
+ 商品名;HV-505HC
95
+ 変更後:メーカー;富士フイルム和光純薬株式会社(万協調達)
96
+ 商品名;ハイビスワコー103
97
+ '
98
+ 変更理由 :
99
+ '現行品のカルボキシビニルポリマー(商品名;HV-505HC)が終売となるため。 '
100
+ 返答:
101
+ '
102
+ - 必要十分な情報が記載されています。修正の必要はありません。
103
+ '
104
+
105
+ - 例3:
106
+ ユーザー:
107
+ 前提知識 :
108
+ '
109
+ 製造設備及び仕込み量の変更
110
+ 変更前;1t乳化機1t仕込み もしくは1t乳化機750kg仕込み
111
+ 変更���;500kg乳化機400kg仕込み
112
+ '
113
+ 変更理由 :
114
+ '日邦薬品工業の販売数量が減ったため、製造量を減らして対応するため。 '
115
+ 返答:
116
+ '
117
+ - 会社名は正式な社名を使用するようにしてください。この場合、『日邦薬品工業株式会社』が適切です。
118
+ - また、文章全体を簡潔にまとめるために、『製造量を減らして対応するため』という部分は削除しても問題ないでしょう。
119
+ - さらに、日邦薬品工業株式会社の役割を明確にするために、『発売元である』という情報を追加してください。
120
+ '
121
+ """
122
+
123
+ user_template_review = """
124
+ ## レビュー例:
125
+ {reasons}
126
+
127
+ ## 前提知識:
128
+ {query_content}
129
+
130
+ ## 変更理由:
131
+ {query_reason}
132
+
133
+ 返答:
134
+ """
src/questions.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+
4
+ def get_questions(type, subtype, subtype2):
5
+ with open(
6
+ f"data/questions/{type}_{subtype}_{subtype2}.json", "r", encoding="utf-8"
7
+ ) as file:
8
+ data = json.load(file)
9
+
10
+ questions = [i["question"] for i in data["items"]]
11
+ return questions
src/utils.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+
3
+
4
+ def load_env_vars():
5
+ load_dotenv()
6
+
7
+
8
+ def get_embedding(text, model="text-embedding-3-large", client=None):
9
+ text = text.replace("\n", " ")
10
+ return client.embeddings.create(input=[text], model=model).data[0].embedding