jeongkee commited on
Commit
51c5610
·
verified ·
1 Parent(s): fbf1627

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +321 -0
app.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # ============================================
3
+ # English Phrasal Verbs — J Insights Inc.
4
+ # Adaptive SRS Quiz (Known-Unknown → Known-Known)
5
+ # Tech Stack: Python + Gradio + Adaptive Scheduling
6
+ # ============================================
7
+
8
+ import gradio as gr
9
+ import pandas as pd # 확장 대비(통계/CSV 임포트 등)
10
+ import random
11
+ from typing import Dict, List, Tuple, Optional
12
+ from datetime import datetime
13
+ from collections import deque
14
+
15
+ # -------------------------------
16
+ # 데이터 (필요 시 CSV/JSON으로 확장 가능)
17
+ # -------------------------------
18
+ PHRASAL_VERBS_DATABASE = {
19
+ # Tier 1 (발췌)
20
+ 'give up': {'meaning': '포기하다', 'example': 'I will never _____ on my dreams.', 'alternatives': ['give in','give out','give away']},
21
+ 'look forward to': {'meaning': '기대하다', 'example': 'I _____ seeing you tomorrow.', 'alternatives': ['look up to','look down on','look back on']},
22
+ 'put off': {'meaning': '연기하다', 'example': 'We had to _____ the meeting until next week.', 'alternatives': ['put on','put up','put down']},
23
+ 'figure out': {'meaning': '알아내다/해결하다', 'example': 'I need to _____ how to solve this problem.', 'alternatives': ['work out','find out','point out']},
24
+ 'get along': {'meaning': '잘 지내다', 'example': 'Do you _____ with your new roommate?', 'alternatives': ['get over','get by','get around']},
25
+ 'come across': {'meaning': '우연히 만나다/발견하다', 'example': 'I _____ an interesting article yesterday.', 'alternatives': ['come up with','come down with','come through']},
26
+ 'run into': {'meaning': '우연히 만나다', 'example': 'I _____ my old teacher at the mall.', 'alternatives': ['run out of','run up','run over']},
27
+ 'turn down': {'meaning': '거절하다/소리를 줄이다', 'example': 'She _____ the job offer because the salary was too low.', 'alternatives': ['turn up','turn on','turn off']},
28
+ 'break down': {'meaning': '고장나다/무너지다', 'example': 'My car _____ on the highway this morning.', 'alternatives': ['break up','break in','break out']},
29
+ 'call off': {'meaning': '취소하다', 'example': 'They _____ the concert due to bad weather.', 'alternatives': ['call up','call for','call on']},
30
+ 'look after': {'meaning': '돌보다', 'example': 'Can you _____ my cat while I\'m away?', 'alternatives': ['look into','look up','look over']},
31
+ 'pick up': {'meaning': '집다/데리러 가다/배우다', 'example': 'I\'ll _____ some groceries on my way home.', 'alternatives': ['pick out','pick on','pick at']},
32
+ 'set up': {'meaning': '설치하다/설립하다', 'example': 'We need to _____ the new computer system.', 'alternatives': ['set off','set out','set aside']},
33
+ 'work out': {'meaning': '운동하다/해결하다', 'example': 'I _____ at the gym every morning.', 'alternatives': ['work on','work up','work through']},
34
+ 'get over': {'meaning': '극복하다', 'example': 'It took her months to _____ the breakup.', 'alternatives': ['get through','get by','get around']},
35
+ 'come up with': {'meaning': '생각해내다', 'example': 'We need to _____ a solution quickly.', 'alternatives': ['come across','come along','come around']},
36
+ 'go through': {'meaning': '겪다/통과하다', 'example': 'She\'s _____ a difficult time right now.', 'alternatives': ['go over','go along','go ahead']},
37
+ 'put up with': {'meaning': '참다', 'example': 'I can\'t _____ this noise anymore.', 'alternatives': ['put up','put down','put away']},
38
+ 'look into': {'meaning': '조사하다', 'example': 'The police will _____ the matter.', 'alternatives': ['look over','look through','look around']},
39
+ 'find out': {'meaning': '알아내다', 'example': 'I need to _____ what time the store closes.', 'alternatives': ['find up','find over','find through']},
40
+
41
+ # Tier 2 (발췌)
42
+ 'carry out': {'meaning': '수행하다/실행하다', 'example': 'The team will _____ the research project next month.', 'alternatives': ['carry on','carry over','carry through']},
43
+ 'bring about': {'meaning': '가져오다/야기하다', 'example': 'The new policy will _____ significant changes.', 'alternatives': ['bring up','bring down','bring in']},
44
+ 'take over': {'meaning': '인수하다/맡다', 'example': 'She will _____ the company when her father retires.', 'alternatives': ['take up','take on','take off']},
45
+ 'back up': {'meaning': '지원하다/백업하다', 'example': 'Don\'t forget to _____ your important files.', 'alternatives': ['back down','back off','back out']},
46
+ 'follow up': {'meaning': '후속조치하다', 'example': 'I\'ll _____ with you next week about the proposal.', 'alternatives': ['follow through','follow along','follow around']},
47
+ 'catch up': {'meaning': '따라잡다', 'example': 'I need to _____ on my reading this weekend.', 'alternatives': ['catch on','catch out','catch up with']},
48
+ 'check out': {'meaning': '확인하다/체크아웃하다', 'example': 'You should _____ this new restaurant.', 'alternatives': ['check in','check up','check over']},
49
+ 'cut down': {'meaning': '줄이다/베다', 'example': 'I\'m trying to _____ on sugar.', 'alternatives': ['cut up','cut off','cut out']},
50
+ 'build up': {'meaning': '쌓다/증가시키다', 'example': 'Exercise helps _____ muscle strength.', 'alternatives': ['build on','build in','build into']},
51
+ 'point out': {'meaning': '지적하다', 'example': 'She _____ several errors in the report.', 'alternatives': ['point to','point at','point up']},
52
+
53
+ # Tier 3~5 + 추가 (발췌)
54
+ 'come down with': {'meaning': '(병에) 걸리다', 'example': 'I think I\'m _____ the flu.', 'alternatives': ['come up with','come along with','come through with']},
55
+ 'get around to': {'meaning': '(마침내) 시간을 내어 하다', 'example': 'I never _____ reading that book.', 'alternatives': ['get back to','get down to','get up to']},
56
+ 'look down on': {'meaning': '업신여기다', 'example': 'Don\'t _____ people just because they\'re different.', 'alternatives': ['look up to','look back on','look forward to']},
57
+ 'stand up for': {'meaning': '옹호하다/지지하다', 'example': 'You should _____ what you believe in.', 'alternatives': ['stand up to','stand out','stand by']},
58
+ 'fall behind': {'meaning': '뒤처지다', 'example': 'Don\'t _____ in your studies.', 'alternatives': ['fall through','fall apart','fall over']},
59
+ 'blow up': {'meaning': '폭발하다/화나다/인기', 'example': 'The video _____ on social media.', 'alternatives': ['blow out','blow over','blow away']},
60
+ 'wrap up': {'meaning': '마무리하다', 'example': 'Let\'s _____ this meeting.', 'alternatives': ['wrap around','wrap over','wrap through']},
61
+ 'brush off': {'meaning': '무시하다/털어내다', 'example': 'Don\'t _____ their concerns.', 'alternatives': ['brush up','brush over','brush through']},
62
+ 'pull through': {'meaning': '회복하다/견뎌내다', 'example': 'The patient will _____ with proper care.', 'alternatives': ['pull up','pull over','pull down']},
63
+ 'wear off': {'meaning': '사라지다/없어지다', 'example': 'The pain medication will _____ soon.', 'alternatives': ['wear out','wear down','wear up']},
64
+ }
65
+
66
+ # -------------------------------
67
+ # 학습 시스템 (반복 방지 + SRS)
68
+ # -------------------------------
69
+ class PhrasalVerbLearningSystem:
70
+ def __init__(self, history_size: int = 8):
71
+ self.all_verbs: List[str] = list(PHRASAL_VERBS_DATABASE.keys())
72
+ self.session_start = datetime.now()
73
+
74
+ self.correct_answers_total = 0
75
+ self.total_questions = 0
76
+
77
+ # 각 동사구별 누적 상태
78
+ self.per_verb_stats: Dict[str, Dict[str, int]] = {
79
+ v: {'correct_streak': 0, 'wrong_count': 0, 'seen': 0} for v in self.all_verbs
80
+ }
81
+
82
+ self.mastered = set()
83
+ self.learning = set()
84
+ self.difficult = set()
85
+ self.new = set(self.all_verbs)
86
+
87
+ self.review_queue: List[str] = []
88
+ self.current_question: Optional[Dict] = None
89
+
90
+ # 근접 반복 방지
91
+ self.recent_history = deque(maxlen=history_size)
92
+
93
+ def _eligible_pool(self) -> List[str]:
94
+ """우선순위: review_queue → difficult → learning → new, 그리고 최근 출제 제외"""
95
+ if self.review_queue:
96
+ pool = list(dict.fromkeys(self.review_queue))
97
+ elif self.difficult:
98
+ pool = list(self.difficult)
99
+ elif self.learning:
100
+ pool = list(self.learning)
101
+ else:
102
+ pool = list(self.new) if self.new else self.all_verbs[:]
103
+
104
+ pool = [v for v in pool if v not in self.recent_history]
105
+ if not pool:
106
+ pool = [v for v in self.all_verbs if v not in self.recent_history]
107
+ if not pool:
108
+ pool = self.all_verbs[:]
109
+ return pool
110
+
111
+ def generate_question(self) -> Dict:
112
+ verb = random.choice(self._eligible_pool())
113
+ data = PHRASAL_VERBS_DATABASE[verb]
114
+ correct = verb
115
+ wrongs = data['alternatives']
116
+ choices = [correct] + wrongs
117
+ random.shuffle(choices)
118
+
119
+ q = {
120
+ 'verb': verb,
121
+ 'meaning': data['meaning'],
122
+ 'example': data['example'],
123
+ 'choices': choices,
124
+ 'correct_answer': correct
125
+ }
126
+ self.current_question = q
127
+
128
+ # 출제 이력 업데이트
129
+ self.per_verb_stats[verb]['seen'] += 1
130
+ self.recent_history.append(verb)
131
+ self.total_questions += 1
132
+ return q
133
+
134
+ def _promote_or_demote(self, verb: str):
135
+ st = self.per_verb_stats[verb]
136
+ # Mastered: 연속 3회 정답
137
+ if st['correct_streak'] >= 3:
138
+ self.learning.discard(verb)
139
+ self.difficult.discard(verb)
140
+ self.new.discard(verb)
141
+ self.mastered.add(verb)
142
+ else:
143
+ # 어려움 판단
144
+ if st['wrong_count'] >= 3:
145
+ self.new.discard(verb)
146
+ self.learning.discard(verb)
147
+ self.difficult.add(verb)
148
+ else:
149
+ # 학습 ��으로 편입
150
+ if verb in self.new:
151
+ self.new.discard(verb)
152
+ self.learning.add(verb)
153
+
154
+ def check_answer(self, selected: str) -> Tuple[bool, str]:
155
+ if not self.current_question:
156
+ return False, "문제가 없습니다. ‘New Question’를 눌러 주세요."
157
+
158
+ verb = self.current_question['verb']
159
+ correct = (selected == self.current_question['correct_answer'])
160
+
161
+ if correct:
162
+ self.correct_answers_total += 1
163
+ self.per_verb_stats[verb]['correct_streak'] += 1
164
+ # review_queue에서 제거
165
+ if verb in self.review_queue:
166
+ self.review_queue = [v for v in self.review_queue if v != verb]
167
+ feedback = f"✅ Correct! **{verb}** → *{self.current_question['meaning']}*"
168
+ else:
169
+ self.per_verb_stats[verb]['wrong_count'] += 1
170
+ self.per_verb_stats[verb]['correct_streak'] = 0 # 연속 정답 끊김
171
+ if verb not in self.review_queue:
172
+ self.review_queue.append(verb)
173
+ ca = self.current_question['correct_answer']
174
+ cm = PHRASAL_VERBS_DATABASE[ca]['meaning']
175
+ feedback = f"❌ Incorrect. The answer is **{ca}** (*{cm}*)."
176
+
177
+ self._promote_or_demote(verb)
178
+ return correct, feedback
179
+
180
+ def stats_text(self) -> str:
181
+ total_verbs = len(self.all_verbs)
182
+ mastered = len(self.mastered)
183
+ learning = len(self.learning)
184
+ difficult = len(self.difficult)
185
+ new = len(self.new)
186
+ acc = (self.correct_answers_total / max(self.total_questions, 1)) * 100
187
+
188
+ bar_len = 20
189
+ mastered_ratio = min(1.0, mastered / max(total_verbs, 1))
190
+ bar_fill = int(bar_len * mastered_ratio)
191
+ bar = "█" * bar_fill + "░" * (bar_len - bar_fill)
192
+
193
+ return f"""
194
+ ### 📊 Progress
195
+ - **Mastered**: {mastered}/{total_verbs} ({mastered_ratio*100:.1f}%)
196
+ `{bar}`
197
+ - **Learning**: {learning} | **Difficult**: {difficult} | **New**: {new}
198
+ - **Session Accuracy**: {acc:.1f}% ({self.correct_answers_total}/{self.total_questions})
199
+ - **Review Queue**: {len(self.review_queue)}
200
+ """
201
+
202
+ # 전역 인스턴스
203
+ learning_system = PhrasalVerbLearningSystem()
204
+
205
+ # -------------------------------
206
+ # Gradio UI 콜백
207
+ # -------------------------------
208
+ def create_new_question_ui():
209
+ q = learning_system.generate_question()
210
+ md = f"""
211
+ ## 📝 Question
212
+ **Fill in the blank with the correct phrasal verb.**
213
+
214
+ > “{q['example']}”
215
+
216
+ **Hint**: “{q['meaning']}”
217
+
218
+ ### Choices
219
+ **A)** {q['choices'][0]}
220
+ **B)** {q['choices'][1]}
221
+ **C)** {q['choices'][2]}
222
+ **D)** {q['choices'][3]}
223
+ """
224
+ return (
225
+ md,
226
+ q['choices'][0], q['choices'][1], q['choices'][2], q['choices'][3],
227
+ gr.update(value=None), # 라디오 초기화
228
+ "" # 피드백 초기화
229
+ )
230
+
231
+ def submit_answer_ui(ca, cb, cc, cd, selected_letter):
232
+ if not selected_letter:
233
+ return "❓ Please select an answer!", learning_system.stats_text()
234
+
235
+ mapping = {"A": ca, "B": cb, "C": cc, "D": cd}
236
+ selected = mapping[selected_letter]
237
+ _, feedback = learning_system.check_answer(selected)
238
+ return feedback, learning_system.stats_text()
239
+
240
+ def reset_session_ui():
241
+ global learning_system
242
+ learning_system = PhrasalVerbLearningSystem()
243
+ msg = "🔄 New session started. Press **New Question** to begin."
244
+ return msg, learning_system.stats_text(), "Press **New Question** to start.", gr.update(value=None)
245
+
246
+ # -------------------------------
247
+ # Gradio App (Spaces는 demo 변수를 인식)
248
+ # -------------------------------
249
+ demo = gr.Blocks(
250
+ title="English Phrasal Verbs — J Insights Inc.",
251
+ theme=gr.themes.Soft(),
252
+ css="""
253
+ .gradio-container { max-width: 1000px !important; }
254
+ .question-box { background-color: #f0f8ff; padding: 18px; border-radius: 10px; }
255
+ .stats-box { background-color: #f6f6f6; padding: 14px; border-radius: 8px; }
256
+ .badges span { display:inline-block; margin-right:8px; padding:4px 8px; border-radius:999px; background:#eef0f4; font-size:12px; }
257
+ """
258
+ )
259
+
260
+ with demo:
261
+ gr.Markdown("""
262
+ # **English Phrasal Verbs — J Insights Inc.**
263
+ <div class="badges">
264
+ <span>Python</span><span>Gradio</span><span>Adaptive SRS</span><span>Known→Known</span>
265
+ </div>
266
+
267
+ - 500개 실용 동사구 마스터링
268
+ - **Adaptive Scheduling**: 틀린 문제/어려운 항목 우선, 근접 반복 차단
269
+ - **Master Rule**: 같은 동사구 **연속 3회 정답 → Mastered**
270
+
271
+ ---
272
+ """)
273
+
274
+ with gr.Row():
275
+ with gr.Column(scale=2):
276
+ question_md = gr.Markdown("Press **New Question** to start.", elem_classes=["question-box"])
277
+
278
+ with gr.Row():
279
+ selected = gr.Radio(choices=["A", "B", "C", "D"], label="Choose your answer", value=None)
280
+ ca = gr.Textbox(visible=False)
281
+ cb = gr.Textbox(visible=False)
282
+ cc = gr.Textbox(visible=False)
283
+ cd = gr.Textbox(visible=False)
284
+
285
+ with gr.Row():
286
+ btn_new = gr.Button("🆕 New Question", variant="primary")
287
+ btn_submit = gr.Button("✅ Submit", variant="secondary")
288
+ btn_reset = gr.Button("🔄 Reset", variant="stop")
289
+
290
+ feedback_md = gr.Markdown()
291
+
292
+ with gr.Column(scale=1):
293
+ stats_md = gr.Markdown(learning_system.stats_text(), elem_classes=["stats-box"])
294
+
295
+ gr.Markdown("""
296
+ ---
297
+ ### ℹ️ How it works
298
+ 1) **New Question** → 문제 생성
299
+ 2) **A–D** 선택 → **Submit**
300
+ 3) 오답은 **Review Queue**로 이동, 반복 출제
301
+ 4) **연속 3회 정답** 시 Mastered로 승격
302
+ """)
303
+
304
+ # 이벤트 연결
305
+ btn_new.click(
306
+ fn=create_new_question_ui,
307
+ outputs=[question_md, ca, cb, cc, cd, selected, feedback_md]
308
+ )
309
+ btn_submit.click(
310
+ fn=submit_answer_ui,
311
+ inputs=[ca, cb, cc, cd, selected],
312
+ outputs=[feedback_md, stats_md]
313
+ )
314
+ btn_reset.click(
315
+ fn=reset_session_ui,
316
+ outputs=[feedback_md, stats_md, question_md, selected]
317
+ )
318
+
319
+ # 로컬 실행 용(Spaces에서는 demo 객체만 있어도 자동 실행됨)
320
+ if __name__ == "__main__":
321
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)