kobkrit commited on
Commit
bf25e2b
·
verified ·
1 Parent(s): d1d6fa5

Upload folder using huggingface_hub

Browse files
.generation_progress.json ADDED
The diff for this file is too large to render. See raw diff
 
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ bazi_training_data.jsonl filter=lfs diff=lfs merge=lfs -text
__pycache__/dataset_generator.cpython-311.pyc ADDED
Binary file (22.7 kB). View file
 
bazi_training_data.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77ab7c88badabeabf1c9141ccfcd0377a1746abcadf87c16a9420c68c0f9c591
3
+ size 1186265441
dataset_generator.py ADDED
@@ -0,0 +1,1072 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Dataset Generator for BaZi Fortune Telling
4
+ Uses Google Gemini Pro 2.5 with thinking to generate high-quality training data
5
+ Supports multithreaded processing for faster generation
6
+ """
7
+
8
+ import json
9
+ import random
10
+ import time
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, List, Optional, Tuple
13
+ import os
14
+ import sys
15
+ from concurrent.futures import ThreadPoolExecutor, as_completed
16
+ from threading import Lock
17
+ import threading
18
+
19
+ # Add parent directory to path to import modules
20
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21
+
22
+ from bazi_calculator import BaziCalculator, compute_birth_pillars
23
+ from bazi_favorable_elements import calculate_favorable_elements
24
+
25
+ # Import Google Gemini
26
+ try:
27
+ from google import genai
28
+ from google.genai import types
29
+ except ImportError:
30
+ print("Please install google-genai: pip install google-genai")
31
+ sys.exit(1)
32
+
33
+ # Gemini API Configuration
34
+ GEMINI_API_KEY = "AIzaSyCqe3vjvPlo1lt_hpQ4nqAC0-_1omva1oc"
35
+ os.environ["GEMINI_API_KEY"] = GEMINI_API_KEY
36
+
37
+ # Configuration for multithreading
38
+ MAX_WORKERS = 10 # Number of concurrent threads for API calls
39
+ RATE_LIMIT_DELAY = 0.5 # Delay between API calls to avoid rate limiting
40
+
41
+ # Thread-safe locks
42
+ file_lock = Lock()
43
+ progress_lock = Lock()
44
+ print_lock = Lock()
45
+ api_call_lock = Lock()
46
+ last_api_call_time = 0
47
+
48
+ # Global context cache for system prompt
49
+ context_cache = None
50
+ cache_lock = Lock()
51
+
52
+
53
+ def format_bazi_production_style(person: Dict, bazi_data: Dict) -> str:
54
+ """Format BaZi data in production website style"""
55
+ lines = []
56
+ birth_date = bazi_data['birth_date']
57
+ pillars = bazi_data['birth_pillars']
58
+ fav_elems = bazi_data['favorable_elements']
59
+ future_data = bazi_data['future_data']
60
+ calculator = bazi_data['calculator']
61
+
62
+ # Current date for query
63
+ current_date = datetime.now()
64
+
65
+ # Header
66
+ lines.append("=== BaZi Data ===")
67
+ lines.append(f"Name: {person['name']}")
68
+ lines.append(f"Birth: {birth_date.strftime('%Y-%m-%d %H:%M:%S')}")
69
+ lines.append(f"Gender: {person['gender']}")
70
+ lines.append(f"Query: {current_date.strftime('%Y-%m-%d %H:%M')}")
71
+
72
+ # Calculate age
73
+ age = current_date.year - birth_date.year
74
+ if current_date.month < birth_date.month or (current_date.month == birth_date.month and current_date.day < birth_date.day):
75
+ age -= 1
76
+ lines.append(f"Age: {age}")
77
+
78
+ # Life Stem (Day Master)
79
+ day_stem = pillars['day'][0]
80
+ # Determine strength (simplified)
81
+ stem_strength = "Strong" if len(fav_elems['favorable']) > len(fav_elems['unfavorable']) else "Weak"
82
+ lines.append(f"Life Stem: {day_stem} ({stem_strength})")
83
+
84
+ # Favorable/Unfavorable elements
85
+ lines.append(f"Favorable: {', '.join(fav_elems['favorable'])}")
86
+ lines.append(f"Unfavorable: {', '.join(fav_elems['unfavorable'])}")
87
+
88
+ # Four Pillars with hidden stems
89
+ lines.append("\n=== Four Pillars ===")
90
+
91
+ # Get hidden stems
92
+ from bazi_calculator import BaziCalculator
93
+ calc_temp = BaziCalculator(birth_date, person['gender'].lower(), pillars)
94
+
95
+ # Year pillar
96
+ year_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['year'][1], [])
97
+ lines.append(f"Year : {pillars['year'][0]}{pillars['year'][1]} (hidden: {', '.join(year_hidden)})")
98
+
99
+ # Month pillar
100
+ month_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['month'][1], [])
101
+ lines.append(f"Month: {pillars['month'][0]}{pillars['month'][1]} (hidden: {', '.join(month_hidden)})")
102
+
103
+ # Day pillar
104
+ day_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['day'][1], [])
105
+ lines.append(f"Day : {pillars['day'][0]}{pillars['day'][1]} (hidden: {', '.join(day_hidden)})")
106
+
107
+ # Hour pillar
108
+ hour_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['hour'][1], [])
109
+ lines.append(f"Hour : {pillars['hour'][0]}{pillars['hour'][1]} (hidden: {', '.join(hour_hidden)})")
110
+
111
+ # Current Luck Pillar
112
+ lines.append("\n=== Current Luck Pillar ===")
113
+ for lp in future_data['luck_pillars']:
114
+ if lp['start_age'] <= age <= lp['end_age']:
115
+ lines.append(f"Age {age}: {lp['heaven']}{lp['earth']}")
116
+ break
117
+
118
+ # Current Period
119
+ lines.append("\n=== Current Period ===")
120
+
121
+ # Annual
122
+ for ap in future_data['annual_pillars']:
123
+ if ap['year'] == current_date.year:
124
+ lines.append(f"Annual: {ap['heaven']}{ap['earth']}")
125
+ break
126
+
127
+ # Monthly
128
+ current_month_key = f"{current_date.year}-{current_date.month:02d}"
129
+ if current_month_key in future_data['monthly_pillars']:
130
+ mp = future_data['monthly_pillars'][current_month_key]
131
+ lines.append(f"Monthly: {mp['heaven']}{mp['earth']}")
132
+
133
+ # Daily
134
+ current_day_key = current_date.strftime("%Y-%m-%d")
135
+ if current_day_key in future_data['daily_pillars']:
136
+ dp = future_data['daily_pillars'][current_day_key]
137
+ lines.append(f"Daily: {dp['heaven']}{dp['earth']}")
138
+
139
+ # Hourly
140
+ current_hour = current_date.hour
141
+ # Map to Chinese hour
142
+ if current_hour == 0:
143
+ chinese_hour = 23
144
+ elif current_hour % 2 == 0:
145
+ chinese_hour = current_hour - 1
146
+ else:
147
+ chinese_hour = current_hour
148
+
149
+ if chinese_hour in future_data['hourly_pillars']:
150
+ hp = future_data['hourly_pillars'][chinese_hour]
151
+ lines.append(f"Hourly: {hp['heaven']}{hp['earth']}")
152
+
153
+ # Future 100 Years
154
+ lines.append("\n=== Future 100 Years ===")
155
+
156
+ # Luck Pillars
157
+ lines.append("\n[Luck Pillars]")
158
+ for lp in future_data['luck_pillars']:
159
+ lines.append(f"Age {lp['start_age']}-{lp['end_age']}: {lp['heaven']}{lp['earth']}")
160
+
161
+ # Annual Pillars - ALL 100 years
162
+ lines.append("\n[Annual Pillars]")
163
+ for ap in future_data['annual_pillars']:
164
+ lines.append(f"{ap['year']}: {ap['heaven']}{ap['earth']}")
165
+
166
+ # Monthly Pillars - Show many months
167
+ lines.append("\n[Monthly Pillars]")
168
+ monthly_keys = sorted(list(future_data['monthly_pillars'].keys()))
169
+ for key in monthly_keys:
170
+ mp = future_data['monthly_pillars'][key]
171
+ lines.append(f"{key}: {mp['heaven']}{mp['earth']}")
172
+
173
+ # Daily Pillars - Show many days
174
+ lines.append("\n[Daily Pillars]")
175
+ daily_keys = sorted(list(future_data['daily_pillars'].keys()))
176
+ for key in daily_keys:
177
+ dp = future_data['daily_pillars'][key]
178
+ lines.append(f"{key}: {dp['heaven']}{dp['earth']}")
179
+
180
+ lines.append("\n[Loaded with AiMing v3]")
181
+
182
+ return '\n'.join(lines)
183
+
184
+ # Load system prompt
185
+ def load_system_prompt() -> str:
186
+ """Load the system prompt from prompt-v3.txt"""
187
+ script_dir = os.path.dirname(os.path.abspath(__file__))
188
+ parent_dir = os.path.dirname(script_dir)
189
+ prompt_path = os.path.join(parent_dir, 'prompt-v3.txt')
190
+ with open(prompt_path, 'r', encoding='utf-8') as f:
191
+ return f.read()
192
+
193
+ def generate_random_birthdays(count: int = 10) -> List[Dict]:
194
+ """Generate random birthdays with varied demographics
195
+
196
+ Args:
197
+ count: Number of random birthdays to generate
198
+ """
199
+ birthdays = []
200
+
201
+ # Define name pools - expanded for more variety
202
+ male_names = ["สมชาย", "วิชัย", "ประยุทธ์", "สมศักดิ์", "วีระ", "ณัฐวุฒิ", "ธนากร", "พงษ์ศักดิ์", "อภิชาติ", "จักรพันธ์",
203
+ "สุรชัย", "ประสิทธิ์", "สมพงษ์", "วิทยา", "นิพนธ์", "สุทธิพงษ์", "อนันต์", "ชัยวัฒน์", "ธีระ", "พิชัย"]
204
+ female_names = ["สมหญิง", "มาลี", "สุดาพร", "วิไลวรรณ", "พรทิพย์", "นันทนา", "จิราภรณ์", "ศิริพร", "อรวรรณ", "ปิยะนุช",
205
+ "วรรณา", "สุภาพร", "รัตนา", "อารีย์", "ปาริชาติ", "สุนิสา", "กาญจนา", "ดวงใจ", "ชนิดา", "พิมพ์ใจ"]
206
+
207
+ # Generate diverse birth years (1960-2005)
208
+ # If count > 46 (years available), allow duplicates
209
+ year_range = list(range(1960, 2006))
210
+ if count <= len(year_range):
211
+ years = random.sample(year_range, count)
212
+ else:
213
+ # For counts > 46, we allow some duplicate years
214
+ years = random.choices(year_range, k=count)
215
+
216
+ for i in range(count):
217
+ gender = random.choice(["Male", "Female"])
218
+ name = random.choice(male_names if gender == "Male" else female_names)
219
+
220
+ # Random month and day
221
+ month = random.randint(1, 12)
222
+ if month in [1, 3, 5, 7, 8, 10, 12]:
223
+ day = random.randint(1, 31)
224
+ elif month in [4, 6, 9, 11]:
225
+ day = random.randint(1, 30)
226
+ else: # February
227
+ day = random.randint(1, 28)
228
+
229
+ # Random time (distributed across all 12 Chinese hours)
230
+ hour = random.randint(0, 23)
231
+ minute = random.randint(0, 59)
232
+
233
+ birthdays.append({
234
+ "name": f"{name}_{i+1}",
235
+ "gender": gender,
236
+ "year": years[i],
237
+ "month": month,
238
+ "day": day,
239
+ "hour": hour,
240
+ "minute": minute,
241
+ "is_twin": False
242
+ })
243
+
244
+ return birthdays
245
+
246
+ def generate_questions() -> List[str]:
247
+ """Generate 10 diverse questions for BaZi consultation"""
248
+ question_templates = [
249
+ # General fortune (expanded)
250
+ "ดวงนี้เป็นอย่างไร",
251
+ "ช่วงไหนในชีวิตจะประสบความสำเร็จ",
252
+ "มีอุปสรรคอะไรที่ต้องระวังบ้าง",
253
+ "ดวงชะตาโดยรวมปีนี้เป็นอย่างไร",
254
+ "ชีวิตโดยรวมจะเป็นอย่างไร",
255
+ "อนาคตของฉันจะเป็นอย่างไร",
256
+ "ปีนี้จะเป็นปีที่ดีไหม",
257
+ "ช่วงชีวิตไหนจะเจอปัญหามาก",
258
+ "จะมีชีวิตที่ดีขึ้นเมื่อไหร่",
259
+ "ดวงชะตาในระยะยาวเป็นอย่างไร",
260
+ "ชีวิตจะมีความสุขไหม",
261
+ "จะพบเจอสิ่งดีๆ เมื่อไหร่",
262
+ "มีเคราะห์อะไรรออยู่ไหม",
263
+ "ดวงจะดีขึ้นเมื่อไหร่",
264
+ "ชีวิตจะราบรื่นไหม",
265
+ "ปีไหนจะเป็นปีทอง",
266
+ "จะมีโชคเมื่อไหร่",
267
+ "ควรระวังอะไรในชีวิต",
268
+ "จะมีอะไรเปลี่ยนแปลงไหม",
269
+ "ดวงชะตาตอนแก่เป็นอย่างไร",
270
+
271
+ # Career questions (expanded)
272
+ "ช่วงนี้งานการเป็นอย่างไรบ้าง มีโอกาสก้าวหน้าไหม",
273
+ "อยากเปลี่ยนงานใหม่ ช่วงไหนเหมาะสม",
274
+ "อยากทำธุรกิจส่วนตัว เหมาะไหม ควรทำธุรกิจประเภทไหน",
275
+ "จะได้เลื่อนตำแหน่งไหม",
276
+ "งานที่ทำอยู่จะมั่นคงไหม",
277
+ "ควรลาออกจากงานไหม",
278
+ "จะตกงานไหม",
279
+ "อาชีพอะไรเหมาะกับดวงนี้",
280
+ "ทำงานกับคนอื่นดีหรือทำงานคนเดียวดี",
281
+ "จะประสบความสำเร็จในอาชีพไหม",
282
+ "เจ้านายเมตตาไหม",
283
+ "ควรทำงานรับราชการหรือเอกชน",
284
+ "จะมีคนอิจฉาในที่ทำงานไหม",
285
+ "ควรเปลี่ยนสายงานไหม",
286
+ "จะได้ทำงานที่ชอบไหม",
287
+ "หุ้นส่วนธุรกิจจะซื่อสัตย์ไหม",
288
+ "ควรทำงานในประเทศหรือต่างประเทศ",
289
+ "จะมีปัญหาในที่ทำงานไหม",
290
+ "ควรรับงานฟรีแลนซ์ไหม",
291
+ "ธุรกิจจะอยู่รอดไหม",
292
+ "ควรขยายธุรกิจไหม",
293
+ "จะมีคู่แข่งทางธุรกิจไหม",
294
+ "ลูกค้าจะพอใจไหม",
295
+ "ควรร่วมทุนกับใครไหม",
296
+ "จะได้โบนัสไหม",
297
+ "งานที่สมัครจะได้ไหม",
298
+ "ควรทำอาชีพเสริมไหม",
299
+ "จะเป็นผู้บริหารไหม",
300
+ "ควรลงทุนในธุรกิจอะไร",
301
+ "จะมีรายได้เสริมไหม",
302
+
303
+ # Love and relationships (expanded)
304
+ "เรื่องความรักเป็นอย่างไรบ้าง จะเจอคนที่ใช่เมื่อไหร่",
305
+ "ความรักกับแฟนปัจจุบันจะไปได้ไกลไหม",
306
+ "ปีนี้มีโอกาสแต่งงานไหม",
307
+ "จะได้เจอแฟนไหม เมื่อไหร่",
308
+ "แฟนจะนอกใจไหม",
309
+ "จะมีคนมาชอบไหม",
310
+ "จะเลิกกับแฟนไหม",
311
+ "ความรักจะลงเอยด้วยดีไหม",
312
+ "คู่ที่เหมาะสมเป็นคนแบบไหน",
313
+ "จะมีกี่ครั้งในชีวิตที่รักจริง",
314
+ "จะโสดไปตลอดชีวิตไหม",
315
+ "ควรแต่งงานกับคนนี้ไหม",
316
+ "จะหย่าร้างไหม",
317
+ "จะกลับมาคืนดีกันไหม",
318
+ "คนที่แอบชอบจะมาบอกรักไหม",
319
+ "จะมีคนที่สามไหม",
320
+ "ควรรอคนเก่าไหม",
321
+ "จะเ��อรักแท้ไหม",
322
+ "แฟนจะดีกับเราไหม",
323
+ "จะมีปัญหาเรื่องความรักไหม",
324
+ "ควรเปิดใจให้คนใหม่ไหม",
325
+ "จะอกหักไหม",
326
+ "คนที่ชอบจะชอบเราไหม",
327
+ "จะแต่งงานกี่ครั้ง",
328
+ "คู่ชีวิตจะมาจากไหน",
329
+ "จะมีลูกกับคนรักไหม",
330
+ "ความรักระยะไกลจะสำเร็จไหม",
331
+ "จะเจอคนรักที่ทำงานไหม",
332
+ "ควรคบคนอายุเท่าไหร่",
333
+ "จะมีความรักออนไลน์ไหม",
334
+
335
+ # Health (expanded)
336
+ "สุขภาพช่วงนี้ต้องระวังอะไรบ้าง",
337
+ "มีปัญหาสุขภาพเรื้อรัง จะหายไหม",
338
+ "ปีนี้จะป่วยไหม",
339
+ "เดือนนี้อาการป่วยจะดีขึ้นไหม",
340
+ "ดวงนี้ป่วยเป็นโรคอะไร",
341
+ "จะมีอายุยืนไหม",
342
+ "ควรระวังโรคอะไรเป็นพิเศษ",
343
+ "การผ่าตัดจะสำเร็จไหม",
344
+ "ควรไปหาหมอไหม",
345
+ "สุขภาพจิตจะแข็งแรงไหม",
346
+ "จะมีอุบัติเหตุไหม",
347
+ "จะมีปัญหาเรื่องน้ำหนักไหม",
348
+ "ควรออกกำลังกายแบบไหน",
349
+ "จะมีปัญหาการนอนไหม",
350
+ "สายตาจะมีปัญหาไหม",
351
+ "ฟันจะมีปัญหาไหม",
352
+ "จะมีอาการแพ้ไหม",
353
+ "ควรกินอาหารแบบไหน",
354
+ "จะเครียดมากไหม",
355
+ "จะมีปัญหาผิวหนังไหม",
356
+ "ระบบย่อยอาหารจะมีปัญหาไหม",
357
+ "จะปวดหัวบ่อยไหม",
358
+ "จะมีปัญหากระดูกไหม",
359
+ "หัวใจจะแข็งแรงไหม",
360
+ "จะมีปัญหาความดันไหม",
361
+ "จะเป็นโรคติดต่อไหม",
362
+ "จะมีปัญหาฮอร์โมนไหม",
363
+ "ควรตรวจสุขภาพเมื่อไหร่",
364
+ "จะติดเชื้อไหม",
365
+ "จะมีปัญหาการหายใจไหม",
366
+
367
+ # Wealth and finance (expanded)
368
+ "การเงินปีนี้เป็นอย่างไร จะมีโชคลาภไหม",
369
+ "อยากลงทุนหุ้น/คริปโต เหมาะไหม",
370
+ "หนี้สินเยอะ จะหมดเมื่อไหร่",
371
+ "ดวงนี้รวยเมื่อไหร่",
372
+ "ดวงนี้รวยไหม",
373
+ "จะถูกหวยไหม",
374
+ "ควรลงทุนอะไร",
375
+ "จะล้มละลายไหม",
376
+ "เงินจะพอใช้ไหม",
377
+ "จะมีคนมาหลอกเงินไหม",
378
+ "ควรให้คนอื่นยืมเงินไหม",
379
+ "จะได้มรดกไหม",
380
+ "ควรซื้อบ้านหรือเช่าบ้าน",
381
+ "การลงทุนจะได้ผลตอบแทนดีไหม",
382
+ "จะมีเงินเก็บไหม",
383
+ "ควรฝากเงินหรือลงทุน",
384
+ "จะได้เงินคืนจากลูกหนี้ไหม",
385
+ "ควรซื้อทองไหม",
386
+ "จะมีรายได้พิเศษไหม",
387
+ "ควรเล่นพนันไหม",
388
+ "จะขาดทุนไหม",
389
+ "ควรกู้เงินไหม",
390
+ "จะมีเงินใช้ตอนแก่ไหม",
391
+ "ควรทำประกันไหม",
392
+ "จะมีปัญหาภาษีไหม",
393
+ "ควรลงทุนอสังหาริมทรัพย์ไหม",
394
+ "จะได้โบนัสพิเศษไหม",
395
+ "ควรเก็บเงินสดไหม",
396
+ "จะมีหนี้นอกระบบไหม",
397
+ "ควรขายทรัพย์สินไหม",
398
+
399
+ # Family (expanded)
400
+ "ลูกๆ จะเรียนหนังสือเก่งไหม",
401
+ "ความสัมพันธ์ในครอบครัวจะราบรื่นไหม",
402
+ "จะมีลูกไหม",
403
+ "พ่อแม่จะมีสุขภาพดีไหม",
404
+ "พี่น้องจะสามัคคีกันไหม",
405
+ "จะมีปัญหากับญาติไหม",
406
+ "ลูกจะกตัญญูไหม",
407
+ "ควรมีลูกกี่คน",
408
+ "จะได้อยู่กับครอบครัวไหม",
409
+ "จะมีปัญหากับพ่อตาแม่ยายไหม",
410
+ "ลูกจะประสบความสำเร็จไหม",
411
+ "จะต้องดูแลพ่อแม่ไหม",
412
+ "พี่น้องจะช่วยเหลือไหม",
413
+ "จะมีปัญหาเรื่องมรดกไหม",
414
+ "ครอบครัวจะอบอุ่นไหม",
415
+ "จะมีใครในครอบครัวป่วยไหม",
416
+ "ลูกจะได้เรียนสูงไหม",
417
+ "จะมีปัญหากับคู่สมรสของลูกไหม",
418
+ "หลานจะน่ารักไหม",
419
+ "จะได้เลี้ยงหลานไหม",
420
+
421
+ # Education and learning (expanded)
422
+ "จะสอบติดไหม",
423
+ "ควรเรียนต่อไหม",
424
+ "สาขาอะไรเหมาะกับฉัน",
425
+ "จะจบการศึกษาไหม",
426
+ "จะได้ทุนการศึกษาไหม",
427
+ "จะเรียนเก่งไหม",
428
+ "ควรเรียนในประเทศหรือต่างประเทศ",
429
+ "จะสอบตกไหม",
430
+ "ควรเรียนพิเศษไหม",
431
+ "จะมีปัญหากับอาจารย์ไหม",
432
+ "จะมีเพื่อนที่ดีไหม",
433
+ "ควรย้ายโรงเรียนไหม",
434
+ "จะได้เกียรตินิยมไหม",
435
+ "ควรเรียนภาษาอะไร",
436
+ "จะมีปัญหาการเรียนไหม",
437
+ "ควรเรียนปริญญาโทไหม",
438
+ "จะได้ไปแลกเปลี่ยนไหม",
439
+ "ควรเรียนออนไลน์ไหม",
440
+ "จะมีปัญหากับเพื่อนร่วมชั้นไหม",
441
+ "ควรเข้ามหาวิทยาลัยไหน",
442
+
443
+ # Specific timing (expanded)
444
+ "เดือนหน้าจะมีเรื่องดีๆ เข้ามาไหม",
445
+ "ปีหน้าควรระวังเรื่องอะไรบ้าง",
446
+ "วันไหนในเดือนนี้เป็นวันมงคล",
447
+ "พรุ่งนี้จะเป็นวันที่ดีไหม",
448
+ "สัปดาห์นี้จะมีข่าวดีไหม",
449
+ "ช่วงไหนของปีจะดีที่สุด",
450
+ "เดือนไหนควรระมัดระวังเป็นพิเศษ",
451
+ "วันนี้ควรทำอะไร",
452
+ "คืนนี้จะนอนหลับสบายไหม",
453
+ "เช้านี้จะมีเรื่องดีไหม",
454
+ "บ่ายนี้ควรระวังอะไร",
455
+ "เย็นนี้จะเจอใคร",
456
+ "วันจันทร์จะเป็นอย่างไร",
457
+ "สุดสัปดาห์นี้จะสนุกไหม",
458
+ "เดือนนี้จะได้เงินไหม",
459
+ "ปีนี้จะมีอะไรเปลี่ยนแปลง",
460
+ "ไตรมาสนี้ธุรกิจจะดีไหม",
461
+ "ฤดูนี้สุขภาพจะเป็นอย่างไร",
462
+ "ช่วงปิดเทอมจะไปไหนไหม",
463
+ "วันเกิดปีนี้จะมีความสุขไหม",
464
+
465
+ # Decision making (expanded)
466
+ "กำลังตัดสินใจเรื่องสำคัญ ควรเลือกทางไหน",
467
+ "จะย้ายบ้านดีไหม ช่วงไหนเหมาะสม",
468
+ "อยากไปเรียนต่อต่างประเทศ เหมาะไหม",
469
+ "ควรซื้อรถไหม",
470
+ "ควรขาย��ี่ดินไหม",
471
+ "ควรเริ่มทำอะไรใหม่ๆ ไหม",
472
+ "ควรรับข้อเสนอนี้ไหม",
473
+ "ควรไปหรือไม่ไป",
474
+ "ควรพูดความจริงไหม",
475
+ "ควรให้อภัยไหม",
476
+ "ควรเสี่ยงไหม",
477
+ "ควรรอหรือรีบทำ",
478
+ "ควรเลือกข้อ A หรือ B",
479
+ "ควรลงทะเบียนไหม",
480
+ "ควรเซ็นสัญญาไหม",
481
+ "ควรไว้ใจคนนี้ไหม",
482
+ "ควรบอกเลิกไหม",
483
+ "ควรเริ่มใหม่ไหม",
484
+ "ควรยอมรับไหม",
485
+ "ควรปฏิเสธไหม",
486
+
487
+ # Travel and relocation (expanded)
488
+ "จะได้ไปต่างประเทศไหม",
489
+ "ควรย้ายไปอยู่ที่อื่นไหม",
490
+ "การเดินทางจะปลอดภัยไหม",
491
+ "ทิศไหนเหมาะกับดวง",
492
+ "ควรอยู่ภาคไหนของประเทศ",
493
+ "จะได้ไปเที่ยวไหม",
494
+ "ควรไปเที่ยวประเทศไหน",
495
+ "จะมีปัญหาระหว่างเดินทางไหม",
496
+ "ควรเดินทางคนเดียวหรือกับคนอื่น",
497
+ "จะพลาดเที่ยวบินไหม",
498
+ "จะทำหนังสือเดินทางหายไหม",
499
+ "ควรพักโรงแรมหรือบ้านเพื่อน",
500
+ "จะมีอุบัติเหตุทางรถไหม",
501
+ "ควรขับรถเองหรือนั่งรถคนอื่น",
502
+ "จะติดอยู่ต่างประเทศไหม",
503
+
504
+ # Legal and disputes (expanded)
505
+ "คดีความจะชนะไหม",
506
+ "จะมีปัญหาทางกฎหมายไหม",
507
+ "ควรฟ้องร้องไหม",
508
+ "จะเจอคนโกงไหม",
509
+ "จะมีปัญหากับเจ้าหน้าที่ไหม",
510
+ "จะถูกฟ้องไหม",
511
+ "ควรยอมความไหม",
512
+ "จะมีปัญหาเอกสารไหม",
513
+ "ทนายความจะช่วยได้ไหม",
514
+ "จะต้องขึ้นศาลไหม",
515
+ "จะมีปัญหาสัญญาไหม",
516
+ "จะถูกหลอกลวงไหม",
517
+ "จะมีคนมาเอาเปรียบไหม",
518
+ "ควรแจ้งความไหม",
519
+ "จะได้ความยุติธรรมไหม",
520
+
521
+ # Spiritual and luck (expanded)
522
+ "ธาตุอะไรเสริมดวง ควรใส่สีอะไร",
523
+ "เลขอะไรเป็นเลขมงคล",
524
+ "ควรบูชาพระเครื่องไหม",
525
+ "ควรทำบุญอะไร",
526
+ "มีกรรมเก่าอะไรติดตัวไหม",
527
+ "ควรแก้ชงอย่างไร",
528
+ "วันไหนเป็นวันดีของฉัน",
529
+ "ควรตั้งชื่อลูกว่าอะไร",
530
+ "ควรไหว้พระวันไหน",
531
+ "ควรสวดมนต์ไหม",
532
+ "จะมีสิ่งศักดิ์สิทธิ์คุ้มครองไหม",
533
+ "ควรใส่เครื่องรางไหม",
534
+ "ควรไปวัดไหนทำบุญ",
535
+ "จะมีบุญจากชาติก่อนไหม",
536
+ "ควรถือศีลไหม",
537
+ "จะมีเทพเจ้าช่วยไหม",
538
+ "ควรบนบานไหม",
539
+ "จะสมหวังที่บนไว้ไหม",
540
+ "ควรเลี้ยงสัตว์อะไร",
541
+ "ต้นไม้อะไรเสริมดวง",
542
+
543
+ # Personal development (expanded)
544
+ "จะพัฒนาตัวเองได้อย่างไร",
545
+ "จุดแข็งของฉันคืออะไร",
546
+ "จุดอ่อนที่ควรแก้ไขคืออะไร",
547
+ "จะมีชื่อเสียงไหม",
548
+ "จะประสบความสำเร็จในชีวิตไหม",
549
+ "ควรทำอะไรเพื่อเปลี่ยนชีว��ต",
550
+ "จะเป็นคนสำคัญไหม",
551
+ "จะมีคนนับถือไหม",
552
+ "ควรเรียนรู้อะไรเพิ่ม",
553
+ "จะมีความสามารถพิเศษไหม",
554
+ "ควรฝึกทักษะอะไร",
555
+ "จะเป็นผู้นำไหม",
556
+ "จะมีคนตามไหม",
557
+ "ควรเปลี่ยนนิสัยอะไร",
558
+ "จะมีความคิดสร้างสรรค์ไหม",
559
+ "ควรทำงานอดิเรกอะไร",
560
+ "จะเก่งด้านไหน",
561
+ "ควรออกจากคอมฟอร์ทโซนไหม",
562
+ "จะมีพรสวรรค์ด้านไหน",
563
+ "ควรฝึกสมาธิไหม",
564
+
565
+ # Crisis and problems (expanded)
566
+ "จะผ่านวิกฤตนี้ไปได้ไหม",
567
+ "ปัญหาจะจบเมื่อไหร่",
568
+ "จะมีคนช่วยเหลือไหม",
569
+ "ควรขอความช่วยเหลือจากใคร",
570
+ "สิ่งที่กังวลจะเกิดขึ้นจริงไหม",
571
+ "จะแก้ปัญหาได้ไหม",
572
+ "ควรหนีปัญหาไหม",
573
+ "จะมีทางออกไหม",
574
+ "ปัญหาจะซ้ำไหม",
575
+ "จะมีปัญหาใหม่ไหม",
576
+ "ควรเผชิญหน้ากับปัญหาไหม",
577
+ "จะมีคนมาสร้างปัญหาไหม",
578
+ "ปัญหาจะใหญ่ขึ้นไหม",
579
+ "ควรปล่อยวางไหม",
580
+ "จะเครียดมากไหม",
581
+
582
+ # Business specific
583
+ "ควรเปิดร้านอาหารไหม",
584
+ "ธุรกิจออนไลน์จะสำเร็จไหม",
585
+ "ควรขายของผ่านโซเชียลไหม",
586
+ "จะมีลูกค้าเยอะไหม",
587
+ "คู่แข่งจะมากไหม",
588
+ "ควรทำธุรกิจครอบครัวไหม",
589
+ "จะขาดทุนในปีแรกไหม",
590
+ "ควรขอสินเชื่อธุรกิจไหม",
591
+ "พนักงานจะซื่อสัตย์ไหม",
592
+ "ควรเปิดสาขาไหม",
593
+
594
+ # Technology and modern life
595
+ "ควรเรียนเขียนโปรแกรมไหม",
596
+ "จะประสบความสำเร็จในสายไอทีไหม",
597
+ "ควรทำช่อง YouTube ไหม",
598
+ "จะดังในโซเชียลมีเดียไหม",
599
+ "ควรเป็นอินฟลูเอนเซอร์ไหม",
600
+ "จะถูกแฮกข้อมูลไหม",
601
+ "ควรลงทุนใน NFT ไหม",
602
+ "จะติดเกมไหม",
603
+ "ควรทำงาน Work from home ไหม",
604
+ "จะมีปัญหากับเทคโนโลยีไหม",
605
+
606
+ # Lifestyle choices
607
+ "ควรเป็นมังสวิรัติไหม",
608
+ "ควรงดเหล้าไหม",
609
+ "ควรเลิกบุหรี่ไหม",
610
+ "ควรทำศัลยกรรมไหม",
611
+ "ควรลดน้ำหนักไหม",
612
+ "ควรย้อมผมไหม",
613
+ "ควรเลี้ยงสัตว์ไหม",
614
+ "ควรอยู่คอนโดหรือบ้าน",
615
+ "ควรมีรถไหม",
616
+ "ควรใช้ชีวิตแบบมินิมอลไหม",
617
+
618
+ # Social relationships
619
+ "จะมีเพื่อนแท้ไหม",
620
+ "เพื่อนจะทรยศไหม",
621
+ "ควรตัดขาดกับเพื่อนคนนี้ไหม",
622
+ "จะมีศัตรูไหม",
623
+ "คนรอบข้างจริงใจไหม",
624
+ "จะถูกนินทาไหม",
625
+ "จะมีคนอิจฉาไหม",
626
+ "ควรให้โอกาสเพื่อนไหม",
627
+ "จะโดนหักหลังไหม",
628
+ "ควรไว้ใจใครไหม",
629
+
630
+ # Special occasions
631
+ "งานแต่งจะสำเร็จไหม",
632
+ "ควรจัดงานใหญ่หรือเล็ก",
633
+ "จะมีคนมาร่วมงานเยอะไหม",
634
+ "ควรเลื��อนงานไหม",
635
+ "งานบวชจะราบรื่นไหม",
636
+ "ควรทำพิธีขึ้นบ้านใหม่ไหม",
637
+ "จะได้รับเชิญไปงานไหม",
638
+ "ควรไปงานศพไหม",
639
+ "วันเกิดปีนี้จะพิเศษไหม",
640
+ "ควรฉลองอะไรไหม",
641
+
642
+ # Retirement and elderly
643
+ "จะเกษียณสุขสบายไหม",
644
+ "ควรเกษียณเมื่อไหร่",
645
+ "จะมีเงินพอใช้ตอนแก่ไหม",
646
+ "ลูกจะดูแลไหม",
647
+ "ควรอยู่บ้านพักคนชราไหม",
648
+ "จะแก่อย่างมีความสุขไหม",
649
+ "จะมีโรคประจำตัวไหม",
650
+ "ควรทำพินัยกรรมไหม",
651
+ "จะได้เห็นหลานโตไหม",
652
+ "ควรย้ายไปอยู่ต่างจังหวัดไหม",
653
+ ]
654
+
655
+ # Randomly select 3 questions
656
+ return random.sample(question_templates, 4)
657
+
658
+ def get_bazi_data(person: Dict) -> Dict:
659
+ """Calculate BaZi data manually"""
660
+
661
+ # Prepare birth datetime
662
+ birth_date = datetime(
663
+ person['year'], person['month'], person['day'],
664
+ person['hour'], person['minute']
665
+ )
666
+
667
+ # Calculate BaZi pillars manually
668
+ birth_pillars = compute_birth_pillars(birth_date)
669
+
670
+ # Calculate favorable elements
671
+ fav_elements = calculate_favorable_elements(birth_pillars)
672
+
673
+ # Generate future BaZi data
674
+ calculator = BaziCalculator(
675
+ birth_datetime=birth_date,
676
+ gender=person['gender'],
677
+ birth_pillars=birth_pillars
678
+ )
679
+
680
+ # Import the generate function from api_v3
681
+ from api_v3 import generate_future_bazi_data
682
+ future_data = generate_future_bazi_data(calculator, years_ahead=100)
683
+
684
+ return {
685
+ 'birth_pillars': birth_pillars,
686
+ 'favorable_elements': fav_elements,
687
+ 'future_data': future_data,
688
+ 'birth_date': birth_date,
689
+ 'calculator': calculator
690
+ }
691
+
692
+ def create_or_get_context_cache(system_prompt: str) -> str:
693
+ """Create or retrieve context cache for the system prompt
694
+
695
+ Returns:
696
+ cache_name: The name/ID of the cached context
697
+ """
698
+ global context_cache
699
+
700
+ with cache_lock:
701
+ if context_cache is not None:
702
+ with print_lock:
703
+ print(" Using existing context cache for system prompt")
704
+ return context_cache
705
+
706
+ try:
707
+ client = genai.Client(api_key=GEMINI_API_KEY)
708
+
709
+ # Create context cache with the system prompt
710
+ with print_lock:
711
+ print(" Creating context cache for system prompt (this saves API costs)...")
712
+
713
+ # Create cache with system instruction using the correct API format
714
+ # Contents is required even when using system_instruction
715
+ cache = client.caches.create(
716
+ model="gemini-2.5-pro", # Use explicit version with context caching support
717
+ config=types.CreateCachedContentConfig(
718
+ display_name='bazi_system_prompt', # Identifier for the cache
719
+ system_instruction=system_prompt, # The long system prompt to cache
720
+ contents=[
721
+ types.Content(
722
+ role="user",
723
+ parts=[types.Part(text="System prompt loaded.")],
724
+ )
725
+ ], # Minimal content required by API
726
+ ttl="72000s", # Cache for 20 hour (in seconds string format)
727
+ )
728
+ )
729
+
730
+ context_cache = cache.name
731
+
732
+ with print_lock:
733
+ print(f" Context cache created: {context_cache}")
734
+ print(f" Cache will save ~{len(system_prompt)//4} tokens per request")
735
+
736
+ return context_cache
737
+
738
+ except Exception as e:
739
+ with print_lock:
740
+ print(f" Warning: Could not create context cache: {e}")
741
+ print(" Falling back to regular API calls (higher cost)")
742
+ return None
743
+
744
+ def call_gemini_api_with_cache(user_prompt: str, cache_name: str = None, system_prompt: str = None, max_retries: int = 3) -> Tuple[str, Optional[str]]:
745
+ """Call Google Gemini using context caching for cost savings
746
+
747
+ Returns:
748
+ Tuple of (response_text, new_cache_name or None)
749
+ """
750
+ client = genai.Client(api_key=GEMINI_API_KEY)
751
+
752
+ for attempt in range(max_retries):
753
+ try:
754
+ # Generate response using cached context if available
755
+ if cache_name:
756
+ try:
757
+ # Use cached context - must use same model as cache
758
+ model = "gemini-2.5-pro"
759
+
760
+ # Prepare content for the user prompt
761
+ contents = [
762
+ types.Content(
763
+ role="user",
764
+ parts=[types.Part(text=user_prompt)],
765
+ )
766
+ ]
767
+
768
+ config = types.GenerateContentConfig(
769
+ temperature=0.7,
770
+ top_k=40,
771
+ top_p=0.95,
772
+ max_output_tokens=7500,
773
+ cached_content=cache_name # Reference the cached content
774
+ )
775
+
776
+ # Generate with cached context
777
+ response = client.models.generate_content(
778
+ model=model,
779
+ contents=contents, # Use Content object format
780
+ config=config
781
+ )
782
+
783
+ # Get the text from response
784
+ if response.text:
785
+ return response.text, cache_name # Return existing cache name
786
+ else:
787
+ raise Exception("No response text generated")
788
+
789
+ except Exception as cache_error:
790
+ # If cache fails, try to recreate it
791
+ if "PERMISSION_DENIED" in str(cache_error) or "not found" in str(cache_error).lower():
792
+ with print_lock:
793
+ print(f" Cache expired or not found, recreating cache...")
794
+
795
+ # Try to recreate cache if system_prompt is provided
796
+ if system_prompt:
797
+ new_cache = create_or_get_context_cache(system_prompt)
798
+ if new_cache:
799
+ cache_name = new_cache
800
+ with print_lock:
801
+ print(f" Cache recreated successfully: {new_cache}")
802
+ # Retry with new cache
803
+ continue
804
+
805
+ # If no system_prompt or cache creation failed, use regular API
806
+ cache_name = None
807
+ else:
808
+ raise cache_error
809
+
810
+ # If cache_name is None or cache failed, use regular API
811
+ if not cache_name:
812
+ # Fallback to regular call without caching
813
+ contents = [
814
+ types.Content(
815
+ role="user",
816
+ parts=[
817
+ types.Part(text=user_prompt),
818
+ ],
819
+ ),
820
+ ]
821
+
822
+ config = types.GenerateContentConfig(
823
+ temperature=0.7,
824
+ top_k=40,
825
+ top_p=0.95,
826
+ max_output_tokens=7500,
827
+ )
828
+
829
+ # Generate response (collecting all chunks)
830
+ response_text = ""
831
+ for chunk in client.models.generate_content_stream(
832
+ model="gemini-2.5-pro", # Use 2.5 pro for non-cached
833
+ contents=contents,
834
+ config=config,
835
+ ):
836
+ if chunk.text:
837
+ response_text += chunk.text
838
+
839
+ if response_text:
840
+ return response_text, None # No cache used
841
+ else:
842
+ raise Exception("No response text generated")
843
+
844
+ except Exception as e:
845
+ error_str = str(e)
846
+ if attempt < max_retries - 1 and ("503" in error_str or "overloaded" in error_str.lower()):
847
+ wait_time = (attempt + 1) * 2
848
+ print(f" API overloaded. Retrying in {wait_time} seconds...")
849
+ import time
850
+ time.sleep(wait_time)
851
+ else:
852
+ raise Exception(f"Gemini API error: {e}")
853
+
854
+ # Keep old function for compatibility
855
+ def call_gemini_api(prompt: str, max_retries: int = 3) -> str:
856
+ """Legacy function - calls the new cached version"""
857
+ response, _ = call_gemini_api_with_cache(prompt, None, None, max_retries)
858
+ return response
859
+
860
+ def generate_dataset_entry(person: Dict, question: str, system_prompt: str, entry_id: str, current: int, total: int, cache_name: str = None) -> Tuple[str, Optional[Dict]]:
861
+ """Generate a single dataset entry (thread-safe) with context caching
862
+
863
+ Returns:
864
+ Tuple of (entry_id, entry_dict or None)
865
+ """
866
+ global last_api_call_time, context_cache
867
+
868
+ try:
869
+ with print_lock:
870
+ print(f" [{current}/{total}] Thread-{threading.current_thread().name}: Processing {question[:50]}...")
871
+
872
+ # Get BaZi data (this is computational, no API call)
873
+ bazi_data = get_bazi_data(person)
874
+
875
+ # Format BaZi data in production style
876
+ bazi_formatted = format_bazi_production_style(person, bazi_data)
877
+
878
+ # Format user prompt in production style
879
+ user_prompt = f"""{bazi_formatted}
880
+
881
+ Question: {question}"""
882
+
883
+ # Rate limiting for API calls
884
+ with api_call_lock:
885
+ current_time = time.time()
886
+ if last_api_call_time > 0:
887
+ elapsed = current_time - last_api_call_time
888
+ if elapsed < RATE_LIMIT_DELAY:
889
+ time.sleep(RATE_LIMIT_DELAY - elapsed)
890
+ last_api_call_time = time.time()
891
+
892
+ # Get response from Gemini using context caching
893
+ if cache_name:
894
+ # When using cache, only send the user prompt
895
+ response, new_cache = call_gemini_api_with_cache(user_prompt, cache_name, system_prompt)
896
+ # Update global cache if it was recreated
897
+ if new_cache and new_cache != cache_name:
898
+ with cache_lock:
899
+ context_cache = new_cache
900
+ with print_lock:
901
+ print(f" Updated global cache to: {new_cache}")
902
+ else:
903
+ # Fallback to combined prompt
904
+ full_prompt = f"{system_prompt}\n\n---\n\n{user_prompt}"
905
+ response, _ = call_gemini_api_with_cache(full_prompt, None, None)
906
+
907
+ with print_lock:
908
+ print(f" Thread-{threading.current_thread().name}: Successfully generated response")
909
+
910
+ # Format as OpenAI training data
911
+ return (entry_id, {
912
+ "messages": [
913
+ {"role": "system", "content": system_prompt},
914
+ {"role": "user", "content": user_prompt},
915
+ {"role": "assistant", "content": response}
916
+ ]
917
+ })
918
+
919
+ except Exception as e:
920
+ with print_lock:
921
+ print(f" Thread-{threading.current_thread().name}: Error - {str(e)}")
922
+ return (entry_id, None)
923
+
924
+ def write_entry_to_file(output_file: str, entry: Dict) -> None:
925
+ """Thread-safe writing to file"""
926
+ with file_lock:
927
+ with open(output_file, 'a', encoding='utf-8') as f:
928
+ f.write(json.dumps(entry, ensure_ascii=False) + '\n')
929
+ f.flush()
930
+
931
+ def save_progress(progress_file: str, processed_entries: set) -> None:
932
+ """Thread-safe progress saving"""
933
+ with progress_lock:
934
+ with open(progress_file, 'w') as pf:
935
+ json.dump({'processed': list(processed_entries)}, pf)
936
+
937
+ def main():
938
+ """Main function to generate the dataset with multithreading and context caching"""
939
+ print(f"Starting dataset generation with {MAX_WORKERS} worker threads...")
940
+ print(f"Using Google Gemini Context Caching to reduce API costs\n")
941
+
942
+ # Load system prompt
943
+ system_prompt = load_system_prompt()
944
+ print(f"System prompt loaded: {len(system_prompt)} characters")
945
+
946
+ # Create context cache for the system prompt to save costs
947
+ cache_name = create_or_get_context_cache(system_prompt)
948
+ if cache_name:
949
+ print(f"✓ Context caching enabled - will save ~{len(system_prompt)//4} tokens per request\n")
950
+ else:
951
+ print("⚠ Context caching disabled - using regular API calls\n")
952
+
953
+ # Output file - use absolute paths based on script location
954
+ script_dir = os.path.dirname(os.path.abspath(__file__))
955
+ output_file = os.path.join(script_dir, "bazi_training_data.jsonl")
956
+ progress_file = os.path.join(script_dir, ".generation_progress.json")
957
+
958
+ # Load progress if exists
959
+ processed_entries = set()
960
+ if os.path.exists(progress_file):
961
+ try:
962
+ with open(progress_file, 'r') as f:
963
+ progress_data = json.load(f)
964
+ processed_entries = set(progress_data.get('processed', []))
965
+ print(f"Resuming from previous run. Already processed: {len(processed_entries)} entries")
966
+ except:
967
+ processed_entries = set()
968
+
969
+ # Count existing entries in JSONL
970
+ existing_count = 0
971
+ if os.path.exists(output_file):
972
+ with open(output_file, 'r', encoding='utf-8') as f:
973
+ existing_count = sum(1 for line in f if line.strip())
974
+ print(f"Found {existing_count} existing entries in {output_file}")
975
+
976
+ # Generate random birthdays with seed for consistency
977
+ random.seed(18) # Fixed seed for reproducible birthdays
978
+ birthdays = generate_random_birthdays(500)
979
+ print(f"Generated {len(birthdays)} random birthdays")
980
+
981
+ # Prepare all tasks
982
+ tasks = []
983
+ current = 0
984
+ total = len(birthdays) * 4 # 4 questions per birthday
985
+
986
+ for person in birthdays:
987
+ # Use seed based on person for consistent questions
988
+ person_seed = hash(f"{person['name']}_{person['year']}_{person['month']}_{person['day']}")
989
+ random.seed(person_seed)
990
+ questions = generate_questions()
991
+
992
+ for question in questions:
993
+ current += 1
994
+ # Create unique ID for this entry
995
+ entry_id = f"{person['name']}_{person['year']}{person['month']:02d}{person['day']:02d}_{hash(question)}"
996
+
997
+ # Skip if already processed
998
+ if entry_id not in processed_entries:
999
+ tasks.append((person, question, system_prompt, entry_id, current, total))
1000
+
1001
+ print(f"\nTotal tasks to process: {len(tasks)}")
1002
+ print(f"Already processed: {len(processed_entries)}")
1003
+ print(f"Remaining: {len(tasks)}")
1004
+
1005
+ if not tasks:
1006
+ print("No new tasks to process. Exiting.")
1007
+ return
1008
+
1009
+ # Process tasks with thread pool
1010
+ generated_count = 0
1011
+ failed_count = 0
1012
+
1013
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
1014
+ # Submit all tasks
1015
+ future_to_task = {}
1016
+ for task in tasks:
1017
+ person, question, system_prompt, entry_id, idx, total = task
1018
+ # Use the latest cache_name (might be updated by other threads)
1019
+ current_cache = context_cache if context_cache else cache_name
1020
+ future = executor.submit(generate_dataset_entry, person, question, system_prompt, entry_id, idx, total, current_cache)
1021
+ future_to_task[future] = (person, question, entry_id)
1022
+
1023
+ # Process completed tasks
1024
+ for future in as_completed(future_to_task):
1025
+ person, question, entry_id = future_to_task[future]
1026
+
1027
+ try:
1028
+ result_entry_id, entry = future.result(timeout=600) # 10 minute timeout per task
1029
+
1030
+ if entry:
1031
+ # Write to file
1032
+ write_entry_to_file(output_file, entry)
1033
+
1034
+ # Update progress
1035
+ with progress_lock:
1036
+ processed_entries.add(result_entry_id)
1037
+ generated_count += 1
1038
+
1039
+ # Save progress after EVERY entry (not just every 10)
1040
+ save_progress(progress_file, processed_entries)
1041
+
1042
+ with print_lock:
1043
+ print(f"✓ Saved entry #{existing_count + generated_count}: {person['name']} - {question[:30]}...")
1044
+ else:
1045
+ failed_count += 1
1046
+ with print_lock:
1047
+ print(f"✗ Failed: {person['name']} - {question[:30]}...")
1048
+
1049
+ except Exception as e:
1050
+ failed_count += 1
1051
+ with print_lock:
1052
+ print(f"✗ Error processing {person['name']}: {str(e)}")
1053
+
1054
+ # Final progress save
1055
+ if generated_count > 0:
1056
+ save_progress(progress_file, processed_entries)
1057
+
1058
+ print(f"\n" + "="*60)
1059
+ print(f"Dataset generation complete!")
1060
+ print(f"Generated: {generated_count} new entries")
1061
+ print(f"Failed: {failed_count} entries")
1062
+ print(f"Total entries in file: {existing_count + generated_count}")
1063
+ print(f"Saved to: {output_file}")
1064
+
1065
+ # Clean up progress file if completed successfully
1066
+ if len(processed_entries) >= total:
1067
+ if os.path.exists(progress_file):
1068
+ os.remove(progress_file)
1069
+ print("Cleaned up progress file (generation complete)")
1070
+
1071
+ if __name__ == "__main__":
1072
+ main()
test_format.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Test BaZi format generation"""
3
+
4
+ import sys
5
+ import os
6
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
7
+
8
+ from datetime import datetime
9
+ from bazi_calculator import BaziCalculator, compute_birth_pillars
10
+ from bazi_favorable_elements import calculate_favorable_elements
11
+ from api_v3 import generate_future_bazi_data
12
+
13
+ # Test person
14
+ person = {
15
+ 'name': 'TestUser',
16
+ 'gender': 'Male',
17
+ 'year': 1990,
18
+ 'month': 5,
19
+ 'day': 15,
20
+ 'hour': 10,
21
+ 'minute': 30,
22
+ 'is_twin': False
23
+ }
24
+
25
+ # Prepare birth datetime
26
+ birth_date = datetime(
27
+ person['year'], person['month'], person['day'],
28
+ person['hour'], person['minute']
29
+ )
30
+
31
+ print(f"Birth date: {birth_date}")
32
+
33
+ # Calculate BaZi pillars manually
34
+ birth_pillars = compute_birth_pillars(birth_date)
35
+ print(f"Birth pillars: {birth_pillars}")
36
+
37
+ # Calculate favorable elements
38
+ fav_elements = calculate_favorable_elements(birth_pillars)
39
+ print(f"Favorable elements: {fav_elements}")
40
+
41
+ # Generate future BaZi data
42
+ calculator = BaziCalculator(
43
+ birth_datetime=birth_date,
44
+ gender=person['gender'],
45
+ birth_pillars=birth_pillars
46
+ )
47
+
48
+ print("Generating future data...")
49
+ future_data = generate_future_bazi_data(calculator, years_ahead=100)
50
+
51
+ print(f"Generated {len(future_data['luck_pillars'])} luck pillars")
52
+ print(f"Generated {len(future_data['annual_pillars'])} annual pillars")
53
+ print(f"Generated {len(future_data['monthly_pillars'])} monthly pillars")
54
+ print(f"Generated {len(future_data['daily_pillars'])} daily pillars")
55
+
56
+ # Now format it
57
+ from dataset.dataset_generator import format_bazi_production_style
58
+
59
+ bazi_data = {
60
+ 'birth_pillars': birth_pillars,
61
+ 'favorable_elements': fav_elements,
62
+ 'future_data': future_data,
63
+ 'birth_date': birth_date,
64
+ 'calculator': calculator
65
+ }
66
+
67
+ formatted = format_bazi_production_style(person, bazi_data)
68
+ print("\n\nFormatted output:")
69
+ print("=" * 50)
70
+ print(formatted)
test_gemini25.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Test Gemini 2.5 Pro with google-genai"""
3
+
4
+ import os
5
+ from google import genai
6
+ from google.genai import types
7
+
8
+ # Set API key
9
+ os.environ["GEMINI_API_KEY"] = "AIzaSyCqe3vjvPlo1lt_hpQ4nqAC0-_1omva1oc"
10
+
11
+ def test_generate():
12
+ print("Testing Gemini 2.5 Pro...")
13
+
14
+ client = genai.Client(
15
+ api_key=os.environ.get("GEMINI_API_KEY"),
16
+ )
17
+
18
+ model = "gemini-2.5-pro"
19
+ contents = [
20
+ types.Content(
21
+ role="user",
22
+ parts=[
23
+ types.Part.from_text(text="Say hello in exactly 5 words"),
24
+ ],
25
+ ),
26
+ ]
27
+
28
+ generate_content_config = types.GenerateContentConfig(
29
+ temperature=0.7,
30
+ max_output_tokens=100,
31
+ )
32
+
33
+ print(f"Calling {model}...")
34
+ response_text = ""
35
+
36
+ try:
37
+ for chunk in client.models.generate_content_stream(
38
+ model=model,
39
+ contents=contents,
40
+ config=generate_content_config,
41
+ ):
42
+ if chunk.text:
43
+ response_text += chunk.text
44
+ print(f"Chunk: {chunk.text}")
45
+
46
+ print(f"\nFull response: {response_text}")
47
+ except Exception as e:
48
+ print(f"Error: {e}")
49
+
50
+ if __name__ == "__main__":
51
+ test_generate()
test_gen.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Test generation to see where it hangs"""
3
+
4
+ import sys
5
+ import os
6
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
7
+
8
+ import asyncio
9
+ from datetime import datetime
10
+ import json
11
+ import httpx
12
+
13
+ # Gemini API Configuration
14
+ GEMINI_API_KEY = "AIzaSyCqe3vjvPlo1lt_hpQ4nqAC0-_1omva1oc"
15
+ GEMINI_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
16
+
17
+ async def test_gemini():
18
+ """Test Gemini API call"""
19
+ print("Testing Gemini API...")
20
+
21
+ prompt = "Say hello in 5 words"
22
+
23
+ headers = {
24
+ "Content-Type": "application/json"
25
+ }
26
+
27
+ data = {
28
+ "contents": [{
29
+ "parts": [{
30
+ "text": prompt
31
+ }]
32
+ }],
33
+ "generationConfig": {
34
+ "temperature": 0.7,
35
+ "topK": 40,
36
+ "topP": 0.95,
37
+ "maxOutputTokens": 100,
38
+ },
39
+ "safetySettings": [
40
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
41
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
42
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
43
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"}
44
+ ]
45
+ }
46
+
47
+ print(f"Calling Gemini at: {GEMINI_ENDPOINT}")
48
+
49
+ try:
50
+ async with httpx.AsyncClient(timeout=30.0) as client:
51
+ response = await client.post(
52
+ f"{GEMINI_ENDPOINT}?key={GEMINI_API_KEY}",
53
+ headers=headers,
54
+ json=data
55
+ )
56
+
57
+ print(f"Response status: {response.status_code}")
58
+
59
+ if response.status_code == 200:
60
+ result = response.json()
61
+ print(f"Full response: {json.dumps(result, indent=2)}")
62
+ if 'candidates' in result and len(result['candidates']) > 0:
63
+ candidate = result['candidates'][0]
64
+ if 'content' in candidate and 'parts' in candidate['content']:
65
+ text = candidate['content']['parts'][0]['text']
66
+ print(f"Extracted text: {text}")
67
+ else:
68
+ print(f"No content/parts in candidate: {candidate}")
69
+ else:
70
+ print(f"No candidates in result")
71
+ else:
72
+ print(f"Error: {response.text}")
73
+ except Exception as e:
74
+ print(f"Exception: {e}")
75
+
76
+ if __name__ == "__main__":
77
+ asyncio.run(test_gemini())