-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathastrology_app.py
2802 lines (2399 loc) · 128 KB
/
astrology_app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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
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
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
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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import swisseph as swe
from datetime import datetime
from geopy.geocoders import Nominatim
import pytz
import streamlit as st
import re
from timezonefinder import TimezoneFinder
from geopy import geocoders
import os
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import plotly.graph_objects as go
import json
import openai
import hashlib
import time
from geopy.exc import GeocoderTimedOut
import geopy
from sklearn.preprocessing import MinMaxScaler
from unidecode import unidecode
import base64
import streamlit.components.v1 as components
with open("google_analytics.html", "r") as f:
html_code = f.read()
components.html(html_code, height=0)
# Đường dẫn tương đối tới thư mục ephemeris (trong cùng thư mục với file Python chính)
relative_ephe_path = os.path.join(os.path.dirname(__file__), 'sweph')
# Đặt đường dẫn tới thư viện Swiss Ephemeris
swe.set_ephe_path(relative_ephe_path)
# Đọc dữ liệu từ các file CSV
financial_traits_path = 'combined_financial_traits.csv'
keyword_to_trait_mapping_df = pd.read_csv('keyword_to_trait_mapping.csv')
product_keywords_path = 'product_keywords.csv'
aspect_path = 'aspect_sc.csv'
# Read data
financial_traits_df = pd.read_csv(financial_traits_path)
keyword_to_trait_mapping = keyword_to_trait_mapping_df.set_index('Keyword').T.to_dict()
product_keywords_df = pd.read_csv(product_keywords_path)
product_keywords = product_keywords_df.set_index('Product').T.to_dict('list')
aspect_influence_factors_df = pd.read_csv(aspect_path)
# Rulers for each zodiac sign
rulers = {
"Aries": "Mars",
"Taurus": "Venus",
"Gemini": "Mercury",
"Cancer": "Moon",
"Leo": "Sun",
"Virgo": "Mercury",
"Libra": "Venus",
"Scorpio": ["Mars", "Pluto"],
"Sagittarius": "Jupiter",
"Capricorn": "Saturn",
"Aquarius": ["Saturn", "Uranus"],
"Pisces": ["Jupiter", "Neptune"]
}
# Ảnh hưởng của hành tinh đối với hành vi tài chính dựa trên khoảng cách từ các hành tinh
planet_impacts = {
'Venus': 0.20,
'Jupiter': 0.15,
'Saturn': 0.15,
'Mars': 0.10,
'Pluto': 0.10,
'Mercury': 0.05
}
# Vị trí của các hành tinh trong cung Hoàng Đạo
zodiac_positions = {
'Sun': 10,
'Moon': 7,
'Mercury': 5,
'Mars': 8,
'Venus': 6,
'Jupiter': 9,
'Saturn': 7,
'Uranus': 4,
'Neptune': 3,
'Pluto': 2
}
# Function to calculate zodiac sign and degree
def get_zodiac_sign_and_degree(degree):
zodiacs = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"
]
sign_index = int(degree / 30)
sign = zodiacs[sign_index]
degree_in_sign = degree % 30
return sign, degree_in_sign
# Đường dẫn tới file cache
CITY_CACHE_FILE = 'city_cache.txt'
LOCATION_CACHE_FILE = 'location_cache.txt'
# Hàm lưu cache của city_suggestions vào file
def save_city_cache_to_file(place, city_suggestions):
with open(CITY_CACHE_FILE, "a") as f:
f.write(f"{place}|{city_suggestions}\n")
# Hàm đọc cache city_suggestions từ file
def read_city_cache():
cache = {}
try:
with open("city_cache.txt", "r") as f:
lines = f.readlines()
for line in lines:
# Kiểm tra xem dòng có thể phân tách thành 2 phần tử hay không
parts = line.strip().split("|")
if len(parts) == 2:
place, city_suggestions = parts
cache[place] = eval(city_suggestions) # Convert the string list back to a list
else:
# Nếu dòng không đủ dữ liệu, bỏ qua hoặc ghi log cảnh báo
print(f"Invalid cache entry: {line}")
except FileNotFoundError:
pass
return cache
# Hàm lưu cache của lat, lon, timezone vào file
def save_location_cache_to_file(place, lat, lon, timezone):
with open(LOCATION_CACHE_FILE, "a") as f:
f.write(f"{place}|{lat}|{lon}|{timezone}\n")
# Hàm đọc cache lat, lon, timezone từ file
def read_location_cache():
cache = {}
try:
with open("location_cache.txt", "r") as f:
lines = f.readlines()
for line in lines:
parts = line.strip().split("|")
# Kiểm tra nếu dòng có đúng 4 phần tử thì mới tiến hành xử lý
if len(parts) == 4:
place, lat, lon, timezone = parts
cache[place] = {
'lat': float(lat),
'lon': float(lon),
'timezone': timezone
}
else:
# Nếu dòng không đủ dữ liệu, ghi log cảnh báo và bỏ qua dòng đó
print(f"Invalid cache entry: {line}")
except FileNotFoundError:
pass
return cache
def normalize_place(place):
# Loại bỏ dấu và chuyển tất cả về chữ thường, đồng thời loại bỏ khoảng trắng
normalized = unidecode(place).replace(" ", "").lower()
# Loại bỏ các ký tự không phải là chữ cái (A-Z)
normalized = re.sub(r'[^a-z]', '', normalized)
return normalized
# Đọc cả hai loại cache từ file
city_cache = read_city_cache()
location_cache = read_location_cache()
# Hàm lấy city_suggestions từ cache hoặc API
def get_city_suggestions(query):
if not query or not query.strip():
# Kiểm tra ngôn ngữ người dùng
if language == "Tiếng Việt":
return ["Vui lòng nhập vào nơi sinh của bạn"]
else:
return ["Please input your birthplace"]
normalized_place = normalize_place(query)
# Kiểm tra xem place đã có trong cache chưa
if normalized_place in city_cache:
return city_cache[normalized_place]
# Nếu không có trong cache hoặc cache trống, gọi API
geolocator = Nominatim(user_agent="astrology_app")
time.sleep(1) # Chờ một giây để tránh giới hạn API
try:
location = geolocator.geocode(query, exactly_one=False, limit=5, language='en')
if location:
result = [f"{loc.address} ({loc.latitude}, {loc.longitude})" for loc in location]
city_cache[normalized_place] = result
save_city_cache_to_file(normalized_place, result)
return result
else:
st.warning("No matching city found.")
except Exception as e:
if normalized_place in city_cache: # Nếu cache đã có place, hiển thị từ cache
return city_cache[normalized_place]
st.error(f"Error occurred: {e}")
return []
# Hàm lấy lat, lon, timezone từ cache hoặc API
def get_location_and_timezone(place):
normalized_place = normalize_place(place)
# Kiểm tra cache trước
if normalized_place in location_cache:
cached_data = location_cache[normalized_place]
lat = cached_data.get('lat')
lon = cached_data.get('lon')
timezone = cached_data.get('timezone')
# Nếu cache có đầy đủ dữ liệu, trả về kết quả từ cache
if lat is not None and lon is not None and timezone is not None:
return lat, lon, timezone
else:
st.warning(f"Cache data for {place} is invalid. Retrieving fresh data.")
# Nếu cache không có hoặc dữ liệu không hợp lệ, gọi API để lấy dữ liệu mới
geolocator = Nominatim(user_agent="astrology_app")
location = geolocator.geocode(place) # Gọi API với chuỗi gốc
if location:
lat, lon = location.latitude, location.longitude
tf = TimezoneFinder()
timezone = tf.timezone_at(lat=lat, lng=lon)
if timezone is None:
st.error(f"Unable to retrieve timezone for location: {place}. Please try again.")
return None, None, None
# Lưu lại kết quả vào cache nếu có dữ liệu hợp lệ
location_cache[normalized_place] = {
'lat': lat,
'lon': lon,
'timezone': timezone
}
save_location_cache_to_file(normalized_place, lat, lon, timezone)
return lat, lon, timezone
else:
st.error(f"Cannot find location for place: {place}")
return None, None, None
# Function to convert decimal degrees to DMS (degrees, minutes, seconds)
def decimal_to_dms(degree):
d = int(degree)
m = int((degree - d) * 60)
s = (degree - d - m / 60) * 3600
return f"{d}° {m}' {s:.2f}\""
# Function to calculate planetary positions
def get_planet_positions(year, month, day, hour, minute, lat, lon, timezone):
tz = pytz.timezone(timezone)
local_datetime = datetime(year, month, day, hour, minute)
local_datetime = tz.localize(local_datetime)
utc_datetime = local_datetime.astimezone(pytz.utc)
jd = swe.julday(utc_datetime.year, utc_datetime.month, utc_datetime.day, utc_datetime.hour + utc_datetime.minute / 60.0)
swe.set_topo(lon, lat, 0)
planets = {
'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY, 'Venus': swe.VENUS,
'Mars': swe.MARS, 'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN,
'Uranus': swe.URANUS, 'Neptune': swe.NEPTUNE, 'Pluto': swe.PLUTO,
'North Node': swe.TRUE_NODE, 'Chiron': swe.CHIRON, 'Lilith': swe.MEAN_APOG,
'True Lilith': swe.OSCU_APOG, 'Vertex': swe.VERTEX, 'Ceres': swe.CERES,
'Pallas': swe.PALLAS, 'Juno': swe.JUNO, 'Vesta': swe.VESTA
}
positions = {}
for planet, id in planets.items():
position, _ = swe.calc_ut(jd, id)
positions[planet] = position[0]
positions['South Node'] = (positions['North Node'] + 180.0) % 360.0
return positions
# Function to calculate houses and ascendant
def calculate_houses_and_ascendant(year, month, day, hour, minute, lat, lon, timezone):
tz = pytz.timezone(timezone)
local_datetime = datetime(year, month, day, hour, minute)
local_datetime = tz.localize(local_datetime)
utc_datetime = local_datetime.astimezone(pytz.utc)
jd = swe.julday(utc_datetime.year, utc_datetime.month, utc_datetime.day,
utc_datetime.hour + utc_datetime.minute / 60.0 + utc_datetime.second / 3600.0)
# House calculation using Placidus system
houses, ascmc = swe.houses(jd, lat, lon, b'P')
ascendant = ascmc[0]
house_cusps = {f'House {i+1}': houses[i] for i in range(12)}
return ascendant, house_cusps
# Function to determine which house a planet is in
def get_house_for_planet(planet_degree, houses):
house_degrees = [houses[f'House {i+1}'] for i in range(12)]
house_degrees.append(house_degrees[0] + 360) # wrap-around for the 12th house
for i in range(12):
start = house_degrees[i]
end = house_degrees[(i+1) % 12]
if start < end:
if start <= planet_degree < end:
return i + 1 # Trả về số nhà thay vì chuỗi "House X"
else: # handle the wrap-around
if start <= planet_degree or planet_degree < end:
return i + 1 # Trả về số nhà thay vì chuỗi "House X"
return 'Unknown' # Default case if no house matches
# Function to display planetary data with house placement
def create_astrology_dataframe(positions, houses, ascendant):
planets_info = []
# Add Ascendant to the list
asc_sign, asc_degree_in_sign = get_zodiac_sign_and_degree(ascendant)
asc_dms = decimal_to_dms(asc_degree_in_sign)
# Append Ascendant first (no need for house ruler)
planets_info.append(["Ascendant", asc_sign, "1", asc_dms]) # Chỉ 4 giá trị
# Append other planets
for planet, degree in positions.items():
sign, degree_in_sign = get_zodiac_sign_and_degree(degree)
dms = decimal_to_dms(degree_in_sign)
house_cup = get_house_for_planet(degree, houses) # Chỉ lấy house cup thay vì house ruler
planets_info.append([planet, sign, house_cup, dms]) # Chỉ 4 giá trị
# Đảm bảo là có đúng 4 cột
df = pd.DataFrame(planets_info, columns=['Planet', 'Zodiac Sign', 'House', 'Degree'])
return df
def create_house_dataframe(houses, ascendant):
house_info = []
# Xác định cung hoàng đạo và độ của Ascendant
asc_sign, asc_degree_in_sign = get_zodiac_sign_and_degree(ascendant)
asc_dms = decimal_to_dms(asc_degree_in_sign)
asc_house_ruler = rulers.get(asc_sign, "Unknown")
if isinstance(asc_house_ruler, list):
asc_house_ruler = ', '.join(asc_house_ruler)
# Append Ascendant with Zodiac Sign, Ruler, and Degree
house_info.append(['Ascendant', asc_sign, asc_house_ruler, asc_dms]) # Sử dụng Ascendant cho nhà 1
# Append other houses with Zodiac Sign and Ruler
for i in range(1, 12):
house_degree = houses[f'House {i+1}']
house_sign, degree_in_sign = get_zodiac_sign_and_degree(house_degree)
dms = decimal_to_dms(degree_in_sign)
house_ruler = rulers.get(house_sign, "Unknown")
if isinstance(house_ruler, list):
house_ruler = ', '.join(house_ruler)
house_info.append([f'House {i+1}', house_sign, house_ruler, dms])
# Tạo DataFrame từ house_info, bao gồm cột House, House Cup, Ruler và Degree
df = pd.DataFrame(house_info, columns=['House', 'House Cup', 'Ruler', 'House Cup Degree'])
return df
# Hàm để tính các góc hợp giữa các hành tinh và góc độ
def calculate_aspects(positions):
aspects = []
planets = list(positions.keys())
aspect_angles = [0, 60, 90, 120, 150, 180]
aspect_names = ['Conjunction', 'Sextile', 'Square', 'Trine', 'Quincunx', 'Opposition']
aspect_orbs = [10.5, 6.1, 7.8, 8.3, 2.7, 10] # Orb for each aspect
for i, planet1 in enumerate(planets):
for planet2 in planets[i + 1:]:
pos1 = positions[planet1]
pos2 = positions[planet2]
angle = abs(pos1 - pos2)
if angle > 180:
angle = 360 - angle
for aspect_angle, orb, name in zip(aspect_angles, aspect_orbs, aspect_names):
if abs(angle - aspect_angle) <= orb:
aspects.append((planet1, planet2, name, round(angle, 2))) # Lưu cả góc độ (angle) đã được làm tròn
break
return aspects
# Hàm để tạo DataFrame cho các góc hợp của người dùng, bổ sung góc độ
def create_aspects_dataframe(aspects, positions):
aspect_data = []
for planet1, planet2, aspect_type, degree in aspects:
# Lấy cung hoàng đạo và độ của hành tinh 1
planet1_sign, planet1_degree_in_sign = get_zodiac_sign_and_degree(positions[planet1])
# Lấy cung hoàng đạo và độ của hành tinh 2
planet2_sign, planet2_degree_in_sign = get_zodiac_sign_and_degree(positions[planet2])
# Thêm dữ liệu vào bảng
aspect_data.append([planet1, planet1_sign, planet1_degree_in_sign, planet2, planet2_sign, planet2_degree_in_sign, aspect_type, degree])
# Tạo DataFrame với các cột cần thiết
df = pd.DataFrame(aspect_data, columns=['Planet 1', 'Zodiac Sign 1', 'Degree in Sign 1', 'Planet 2', 'Zodiac Sign 2', 'Degree in Sign 2', 'Aspect', 'Degree'])
return df
# --------------------------------TRAITs-----------------------------------------------
# Hàm xác định hành tinh dẫn dắt
def get_dominant_planet(planet1, planet2, individual_planets):
if planet1 not in zodiac_positions or planet2 not in zodiac_positions:
return None # Bỏ qua nếu hành tinh không trong danh sách
planet1_power = zodiac_positions.get(planet1, 0)
planet2_power = zodiac_positions.get(planet2, 0)
try:
planet1_sign = [sign for planet, sign in individual_planets if planet == planet1][0]
except IndexError:
planet1_sign = None
try:
planet2_sign = [sign for planet, sign in individual_planets if planet == planet2][0]
except IndexError:
planet2_sign = None
if planet1_sign and (planet1 in rulers.get(planet1_sign, []) if isinstance(rulers.get(planet1_sign), list) else planet1 == rulers.get(planet1_sign)):
return planet1
if planet2_sign and (planet2 in rulers.get(planet2_sign, []) if isinstance(rulers.get(planet2_sign), list) else planet2 == rulers.get(planet2_sign)):
return planet2
if planet1_power > planet2_power:
dominant_planet = planet1
elif planet2_power > planet1_power:
dominant_planet = planet2
else:
dominant_planet = planet1
return dominant_planet
# Định dạng các góc hợp từ bảng aspects
def format_aspects(row, individual_planets):
# Lấy nội dung từ cột 'Aspects'
aspects = row['Aspects']
# Sử dụng regular expression để trích xuất planet và aspect type
aspects_list = df_aspects[['Planet 1', 'Planet 2', 'Aspect']].values.tolist()
formatted_aspects = []
for planet1, planet2, aspect_type in aspects_list:
if planet1 not in zodiac_positions or planet2 not in zodiac_positions:
continue
dominant_planet = get_dominant_planet(planet1, planet2, individual_planets)
if dominant_planet == planet2:
planet1, planet2 = planet2, planet1
formatted_aspects.append(f"{planet1} {aspect_type} {planet2}")
return "\n".join(formatted_aspects)
# Trích xuất các góc hợp liên quan đến các hành tinh đã chọn
def extract_relevant_aspects(formatted_aspects, relevant_planets):
aspects = re.findall(r"(\w+)\s+(Conjunction|Sextile|Square|Trine|Opposition|Quincunx)\s+(\w+)", formatted_aspects)
# Chỉ giữ lại các góc hợp liên quan đến các hành tinh trong relevant_planets
filtered_aspects = []
for aspect in aspects:
planet1, aspect_type, planet2 = aspect
if planet1 in relevant_planets and planet2 in relevant_planets:
filtered_aspects.append((planet1, aspect_type, planet2))
else:
print(f"Aspect không hợp lệ: {planet1} hoặc {planet2}")
return filtered_aspects
# Tính toán các đặc điểm tài chính dựa trên vị trí hành tinh và góc hợp
def calculate_financial_traits(individual_planets, formatted_aspects):
# Danh sách các hành tinh mà bạn muốn lấy
selected_planets = ['Sun', 'Moon', 'Mercury', 'Mars', 'Venus', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']
individual_planets = [(row['Planet'], row['Zodiac Sign']) for _, row in df_positions.iterrows() if row['Planet'] in selected_planets]
final_scores = {trait: 0 for trait in ['Adventurous', 'Convenient', 'Impulsive', 'Conservative', 'Cautious', 'Analytical']}
# Tính điểm dựa trên vị trí của các hành tinh và cung hoàng đạo
for planet, sign in individual_planets:
planet_scores_df = financial_traits_df[financial_traits_df['Planet'] == f"{planet} {sign}"]
if not planet_scores_df.empty:
planet_scores = planet_scores_df.iloc[0]
# print(planet_scores)
for trait in final_scores.keys():
base_score = planet_scores[trait]
if planet in planet_impacts:
base_score += planet_impacts[planet]
# print(base_score)
final_scores[trait] += base_score
# print(final_scores)
# print(final_scores)
# Khởi tạo tổng ảnh hưởng của các góc hợp cho từng đặc điểm
aspects = extract_relevant_aspects(formatted_aspects, [planet[0] for planet in individual_planets])
# print(f"Extracted Aspects: {aspects}")
# Initialize scores
final_scores = {trait: 0 for trait in ['Adventurous', 'Convenient', 'Impulsive', 'Conservative', 'Cautious', 'Analytical']}
# Compute base scores from planets and zodiac signs
for planet, sign in individual_planets:
planet_scores_df = financial_traits_df[financial_traits_df['Planet'] == f"{planet} {sign}"]
if not planet_scores_df.empty:
planet_scores = planet_scores_df.iloc[0]
for trait in final_scores.keys():
base_score = planet_scores[trait]
if planet in planet_impacts:
base_score += planet_impacts[planet]
# print(base_score)
final_scores[trait] += base_score
# print(final_scores)
# print(final_scores)
# Initialize total aspect influence
aspects = extract_relevant_aspects(formatted_aspects, [planet[0] for planet in individual_planets])
total_aspect_influence = {trait: 0 for trait in final_scores.keys()}
# Processing aspects from the CSV and checking if the data is being matched
for _, row in df_aspects.iterrows():
planet1, planet2, aspect_type = row['Planet 1'], row['Planet 2'], row['Aspect']
dominant_planet = get_dominant_planet(planet1, planet2, individual_planets)
if dominant_planet == planet2:
planet1, planet2 = planet2, planet1
# Check for matching aspect
matching_aspect_df = aspect_influence_factors_df[
(aspect_influence_factors_df['Planet1'] == planet1) &
(aspect_influence_factors_df['Planet2'] == planet2) &
(aspect_influence_factors_df['Aspect'] == aspect_type)
]
if not matching_aspect_df.empty:
for trait in final_scores.keys():
aspect_influence_value = matching_aspect_df.iloc[0].get(trait)
if aspect_influence_value:
aspect_influence = float(aspect_influence_value.strip('%')) / 100
total_aspect_influence[trait] += aspect_influence
# print(total_aspect_influence)
# Apply aspect influence to the final scores
for trait in final_scores.keys():
adjusted_score = final_scores[trait] + (total_aspect_influence[trait] * 10)
final_scores[trait] = adjusted_score
# Normalize final scores to be between 0 and 5
for trait in final_scores.keys():
final_scores[trait] /= len(individual_planets)
final_scores[trait] = min(max(final_scores[trait], 0), 5)
print(final_scores)
return final_scores
# -------------------------------------DRAW RADAR CHART----------------------------------
# Dictionary mapping traits from English to Vietnamese
# Tạo từ điển để dịch các traits
trait_translation = {
"Adventurous": "Mạo Hiểm",
"Convenient": "Tiện Lợi",
"Impulsive": "Phóng Khoáng",
"Conservative": "Kiên Định",
"Analytical": "Tỉ Mỉ",
"Cautious": "Cẩn Trọng"
}
# Lấy danh sách traits theo ngôn ngữ đã chọn
def get_traits_by_language(language):
traits_english = list(trait_translation.keys()) # Danh sách tiếng Anh
if language == "Tiếng Việt":
return [trait_translation[trait] for trait in traits_english] # Chuyển sang tiếng Việt
else:
return traits_english # Trả về tiếng Anh
# Định nghĩa hàm để xác định mức độ dựa trên điểm
def get_score_level(score, language="Tiếng Việt"):
if language == "Tiếng Việt":
if 1.00 <= score <= 1.80:
return "Rất thấp"
elif 1.81 <= score <= 2.60:
return "Thấp"
elif 2.61 <= score <= 3.40:
return "Trung bình"
elif 3.41 <= score <= 4.20:
return "Cao"
elif 4.21 <= score <= 5.00:
return "Rất cao"
elif language == "English":
if 1.00 <= score <= 1.80:
return "Incredibly low"
elif 1.81 <= score <= 2.60:
return "Low"
elif 2.61 <= score <= 3.40:
return "Average"
elif 3.41 <= score <= 4.20:
return "High"
elif 4.21 <= score <= 5.00:
return "Incredibly high"
# Function to get hover text color based on score level
def get_score_color(score, language="Tiếng Việt"):
level = get_score_level(score, language)
if level in ["Rất thấp", "Incredibly low"]:
return "#ff0000" # Pastel Red
elif level in ["Thấp", "Low"]:
return "#ff5f00" # Pastel Orange
elif level in ["Trung bình", "Average"]:
return "#ffaf00" # Pastel Yellow
elif level in ["Cao", "High"]:
return "#008700" # Pastel Green
else: # "Rất cao" or "Incredibly high"
return "#005fff" # Pastel Blue
# Hàm vẽ radar chart tương tác với plotly
def plot_radar_chart(final_scores, average_scores, language):
# Lấy danh sách traits bằng tiếng Anh và theo ngôn ngữ đã chọn
traits_english = list(trait_translation.keys())
traits = get_traits_by_language(language)
# Lấy điểm số từ final_scores và average_scores dựa trên traits tiếng Anh
scores = [final_scores[trait] for trait in traits_english]
avg_scores = [average_scores[trait] for trait in traits_english]
# Bổ sung giá trị đầu tiên vào cuối để tạo vòng tròn khép kín
traits += [traits[0]]
scores += [scores[0]]
avg_scores += [avg_scores[0]]
# Tạo radar chart với plotly
fig = go.Figure()
# Tạo dữ liệu hover với cả điểm và mức độ của từng trait
# Tùy chỉnh nhãn cho điểm và mức độ theo ngôn ngữ
score_label = "Điểm" if language == "Tiếng Việt" else "Score"
level_label = "Mức độ" if language == "Tiếng Việt" else "Level"
# Tạo nội dung hover với ngôn ngữ tương ứng
hover_texts_avg = [
f"{score_label}: {score:.2f}<br>{level_label}: {get_score_level(score, language)}"
for score in avg_scores
]
hover_texts_user = [
f"{score_label}: <b>{score:.2f}</b><br>{level_label}: <b>{get_score_level(score, language)}</b>"
for score in scores
]
# Thiết lập tên dựa trên lựa chọn ngôn ngữ
your_trait_name = "Điểm của Bạn" if language == "Tiếng Việt" else "Your Trait"
average_scores_name = "Điểm Trung Bình" if language == "Tiếng Việt" else "Average Scores"
# Thêm đường của Average Scores với thông tin hover
fig.add_trace(go.Scatterpolar(
r=avg_scores,
theta=traits,
fill='toself',
name=average_scores_name,
line=dict(color='rgba(0, 0, 255, 0.35)', dash='dashdot'), # A lighter blue to simulate transparency
fillcolor='rgba(204, 204, 255, 0.35)',
hoverinfo='text',
# hovertext=hover_texts_avg # Hiển thị hover text
))
# Thêm đường của Your Scores với thông tin hover
fig.add_trace(go.Scatterpolar(
r=scores,
theta=traits,
fill='toself',
name=f'<span style="color:white;">{your_trait_name}</span>',
line=dict(color='orange'),
fillcolor='rgba(255, 165, 0, 0.25)',
hoverinfo='text',
hovertext=hover_texts_user, # Display hover text
marker=dict(
size=9 # Increase the size of the dots
# color='orange', # Color of the dots
)
))
# Cài đặt layout được tùy chỉnh để cải thiện bố cục
fig.update_layout(
plot_bgcolor='rgba(0, 0, 0, 0)', # Nền của chính radar chart
paper_bgcolor='rgba(0, 0, 0, 0)', # Nền của toàn biểu đồ
polar=dict(
bgcolor="rgba(0, 0, 0, 0)", # Nền trong suốt hoặc màu sắc nhẹ
radialaxis=dict(
visible=True,
range=[0, 5], # Giữ lại range từ 0-5
showticklabels=False, # Ẩn nhãn tick
ticks='',
gridcolor="#756075", # Màu lưới nhẹ
gridwidth=1.5, # Độ dày lưới vừa phải
griddash='dashdot',
linecolor="rgba(0, 0, 0, 0)",
),
angularaxis=dict(
visible=True,
tickfont=dict(size=12, color="rgba(150, 150, 255, 1)", family="Times"), # Kích thước font cho nhãn
rotation=45, # Xoay nhãn để trông đỡ bị chồng chéo
direction="clockwise", # Điều chỉnh hướng nhãn
linewidth=1.5, # Độ dày của trục góc
gridcolor="#756075",
linecolor="#756075" , # Đặt màu trắng để lưới góc không quá rõ
),
),
showlegend=True, # Hiển thị chú thích
hoverlabel=dict(
bgcolor="white", # Nền trắng
font_size=16, # Kích thước chữ to hơn
font_color="black" # Màu chữ đen
),
font=dict(size=12), # Kích thước font tổng thể
margin=dict(l=40, r=40, b=40, t=40), # Điều chỉnh lề cho cân đối
# paper_bgcolor="rgba(240, 240, 240, 1)", # Màu nền tổng thể nhẹ
dragmode="pan" # Cho phép kéo (xoay) nhưng tắt zoom
)
# Thêm màu sắc và kích thước cho các traits
colors = [ '#2774ae', '#9D03C7','#d94b24', '#ff9425','#f1d800','green'] # xanh - tim - đỏ - cam - lá - vàng
if len(traits) > len(colors):
# Extend the colors list by repeating it as needed
colors = colors * (len(traits) // len(colors) + 1)
fig.update_layout(
polar=dict(
angularaxis=dict(
tickvals=[0, 1, 2, 3, 4, 5],
ticktext=[f'<b style="color:{colors[i]}; font-family:Source Sans Pro, sans-serif; font-size:17px;">{traits[i]}</b>' for i in range(len(traits))],
tickmode='array',
)
),
transition={
'duration': 1000,
'easing': 'cubic-in-out'
}
)
# Cập nhật hover label màu theo điểm số
fig.update_traces(
hoverlabel=dict(
font=dict(size=16, color=[get_score_color(score, language) for score in scores]),
bgcolor='white' # Mỗi điểm có màu tương ứng với mức độ
)
)
# Hiển thị biểu đồ trên Streamlit
st.plotly_chart(fig, use_container_width=True)
# ---------------------NHẬN XÉT---------------------------------
# ____________________CHỌN NGÔN NGỮ__________________________
languages = ["Tiếng Việt", "English"]
# Thiết lập mặc định English
default_language = "Tiếng Việt"
# Cho phép người dùng chọn ngôn ngữ
language = st.sidebar.selectbox("Chọn ngôn ngữ / Language settings", languages, index=languages.index(default_language))
#----------------------CALL API-----------------------------------
# Hàm lấy nhận xét dựa trên điểm số và trait
# Đặt API key của OpenAI
openai.api_key = os.getenv("OPENAI_API_KEY")
# testme = os.getenv("OPENAI_API_KEY")
# print(testme)
# client = openai(
# api_key=os.getenv("OPENAI_API_KEY"),
# )
# Hàm gọi GPT để sinh nội dung dựa trên input
def generate_content_with_gpt(prompt, model="gpt-4o-mini", max_tokens=9000):
try:
# neu version new
response = openai.chat.completions.create(
model=model,
messages=[
{"role": "user", "content": prompt}
],
max_tokens=max_tokens,
temperature=0.7
)
# Lấy nội dung phản hồi từ GPT
print(response)
return response.choices[0].message.content.strip()
except Exception as e:
print(f"Lỗi: {e}")
return None
# Hàm để đọc prompt từ file .txt
def load_prompt_from_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
except FileNotFoundError:
print(f"File {file_path} không tồn tại.")
return None
# Hàm lưu danh sách user_hash vào file
def save_user_hash(user_hash):
with open('txt1.txt', 'a') as file: # Sử dụng chế độ 'a' để append
file.write(user_hash + "\n") # Ghi user_hash trên một dòng, thêm ký tự xuống dòng
# Hàm khôi phục danh sách user_hash từ file
def load_user_hash():
try:
with open('txt1.txt', 'r') as file:
return [line.strip() for line in file.readlines()] # Đọc từng dòng và loại bỏ khoảng trắng
except FileNotFoundError:
return []
def append_report_cache_to_txt(user_hash, financial_traits_text, top_traits_description, eligible_content, necessary_content):
with open('txt3.txt', 'a') as file: # Chế độ 'a' để thêm vào file thay vì ghi đè
financial_traits_text = financial_traits_text.replace('\n', '\\n')
top_traits_description = top_traits_description.replace('\n', '\\n')
eligible_content = eligible_content.replace('\n', '\\n')
# Kiểm tra nếu necessary_content không phải None
if necessary_content is not None:
necessary_content = necessary_content.replace('\n', '\\n')
else:
necessary_content = "" # Nếu None, lưu chuỗi rỗng
# Ghi dữ liệu vào file
file.write(f"{user_hash}|{financial_traits_text}|{top_traits_description}|{eligible_content}|{necessary_content}\n")
# Hàm khôi phục report_cache từ file txt (bao gồm sản phẩm)
def load_report_cache_from_txt():
try:
report_cache = {}
with open('txt3.txt', 'r') as file:
for line in file.readlines():
parts = line.strip().split('|', 4) # Chỉnh thành 4 phần
if len(parts) == 5:
user_hash = parts[0]
financial_traits_text = parts[1].replace('\\n', '\n')
top_traits_description = parts[2].replace('\\n', '\n')
eligible_content = parts[3].replace('\\n', '\n')
necessary_content = parts[4].replace('\\n', '\n') if parts[4] else None
# Lưu tất cả 4 giá trị vào cache
report_cache[user_hash] = (
financial_traits_text,
top_traits_description,
eligible_content,
necessary_content
)
else:
print(f"Dòng không hợp lệ: {line.strip()}")
return report_cache
except FileNotFoundError:
print("File không tồn tại, khởi tạo dữ liệu mới")
return {}
# Create a hash based on user information (birth date, time, and place)
def generate_user_hash(birth_date, birth_time, birth_place, language):
unique_string = f"{birth_date}_{birth_time}_{birth_place}_{language}"
return hashlib.md5(unique_string.encode()).hexdigest()
# Hàm để tính toán độ tuổi từ ngày sinh
def calculate_age(birth_date):
today = datetime.today()
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
return age
# Hàm điều chỉnh giọng văn dựa trên độ tuổi
def adjust_tone_based_on_age(age):
if age < 6:
return "giọng văn nhẹ nhàng và dễ hiểu dành cho trẻ nhỏ", "nhỏ tuổi"
elif 6 <= age < 19:
return "giọng văn thân thiện và gần gũi cho lứa tuổi học sinh", "học sinh"
elif 19 <= age < 26:
return "giọng văn năng động và hợp thời, phù hợp với sinh viên", "sinh viên"
elif 26 <= age < 41:
return "giọng văn chuyên nghiệp và cụ thể, phù hợp với người đang đi làm", "đang đi làm"
else:
return "giọng văn trang trọng và rõ ràng, dành cho người lớn tuổi", "lớn tuổi"
def determine_score_level_and_description(trait, score):
if 1.00 <= score <= 1.80:
score_level = "rất thấp"
score_description = f"Người dùng gần như không có biểu hiện mạnh mẽ trong {trait}, cho thấy xu hướng ít quan tâm đến khía cạnh này trong chi tiêu."
elif 1.81 <= score <= 2.60:
score_level = "thấp"
score_description = f"Người dùng có biểu hiện yếu trong {trait}, cho thấy họ có xu hướng tránh {trait} hoặc không thường xuyên thể hiện tính cách này trong các quyết định chi tiêu."
elif 2.61 <= score <= 3.40:
score_level = "trung bình"
score_description = f"Người dùng thể hiện sự cân bằng trong {trait}. Mặc dù {trait} không phải là đặc điểm nổi trội, nhưng họ có khả năng sử dụng nó trong một số quyết định chi tiêu nhất định."
elif 3.41 <= score <= 4.20:
score_level = "cao"
score_description = f"Người dùng có xu hướng thể hiện {trait} thường xuyên trong các quyết định chi tiêu, cho thấy họ có xu hướng mạnh mẽ về đặc điểm này."
else:
score_level = "rất cao"
score_description = f"Người dùng thể hiện {trait} một cách nổi bật, rất quyết đoán và mạnh mẽ, thường xuyên dựa vào đặc điểm này để đưa ra các quyết định chi tiêu."
return score_level, score_description
# Hàm để sinh mô tả trait dựa trên GPT và độ tuổi
def get_trait_description_with_gpt(trait, score, language, age):
# Đọc prompt từ file
prompt_template = load_prompt_from_file('prompt_template.txt')
# Kiểm tra nếu không có prompt nào
if prompt_template is None:
return "Không có mô tả hợp lệ."
# Điều chỉnh giọng văn và nhóm tuổi dựa trên độ tuổi
tone, age_group = adjust_tone_based_on_age(age)
# Gọi hàm xác định mức độ điểm số và mô tả
score_level, score_description = determine_score_level_and_description(trait, score)
# Tạo prompt bằng cách thay thế các biến
prompt = prompt_template.format(
trait=trait,
score=score,
score_level=score_level,
score_description=score_description,
language=language,
tone=tone,
age_group=age_group
)
# Gọi GPT để sinh nội dung
return generate_content_with_gpt(prompt)
# Hàm để sinh mô tả top 3 traits dựa trên GPT và độ tuổi
def get_top_traits_description_with_gpt(top_3_traits, final_scores, language, age):
# Đọc prompt từ file (giả sử bạn có một file riêng cho top 3 traits)
prompt_template = load_prompt_from_file('top_3_traits_template.txt')
# Kiểm tra nếu không có prompt nào
if prompt_template is None:
return "Không có mô tả hợp lệ."
# Điều chỉnh giọng văn và nhóm tuổi dựa trên độ tuổi
tone, age_group = adjust_tone_based_on_age(age)
# Gọi hàm xác định mức độ điểm số và mô tả
# score_level, score_description = determine_score_level_and_description(trait, score)
# Chuẩn bị nội dung top 3 traits để điền vào prompt
traits_info = []
# for trait in top_3_traits: # Thêm vòng lặp cho top 3 traits
for trait in final_scores: # Lặp qua tất cả các traits trong final_scores
score = final_scores[trait] # Lấy điểm số của trait hiện tại từ final_scores
score_level, score_description = determine_score_level_and_description(trait, score)
traits_info.append(f"Trait: {trait}, Score: {score} ({score_level}) - {score_description}")
# Tạo prompt bằng cách thay thế các biến
prompt = prompt_template.format(top_3_traits=', '.join(top_3_traits),traits_info='\n'.join(traits_info), language=language, tone=tone, age_group=age_group)
# Gọi GPT để sinh nội dung
return generate_content_with_gpt(prompt)
# Hàm để lọc và lấy nội dung cần thiết từ phản hồi GPT (nếu cần)
def extract_content_from_gpt_response(response):
# Giả sử bạn muốn lấy nội dung sau tiêu đề "Mô tả:"
match = re.search(r"Mô tả:\s*(.+)", response)
if match:
return match.group(1) # Lấy phần nội dung sau "Mô tả:"
return response # Nếu không có tiêu đề, trả về toàn bộ phản hồi
# Hàm để tính top 3 traits dựa trên final_scores
def get_top_3_traits(final_scores):
# Sắp xếp final_scores theo giá trị (điểm) giảm dần và lấy ra 3 trait đầu tiên
sorted_traits = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)
return [trait for trait, _ in sorted_traits[:3]] # Trả về 3 traits có điểm cao nhất
# Hàm để lấy trait cao nhất và thấp nhất
def get_highest_and_lowest_trait(final_scores):
sorted_traits = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)
highest_trait = sorted_traits[0][0] # Trait có điểm cao nhất
lowest_trait = sorted_traits[-1][0] # Trait có điểm thấp nhất
return highest_trait, lowest_trait
# -----------------CALL API ĐỂ RA ĐOẠN VĂN MÔ TẢ ĐIỂM PRODUCT-----------------------------------------------------------------------
# Dictionary for product names in both languages
product_translation = {
"goal-save": {"vi": "Mục Tiêu Cá Nhân", "en": "Goal Save"},
"money-pot": {"vi": "Hũ Chi Tiêu", "en": "Money Pot"},
"shared-pot": {"vi": "Hũ Chi Tiêu Chung", "en": "Shared Pot"},
"pfm": {"vi": "Quản Lý Tài Chính Cá Nhân", "en": "Personal Financial Management"},
"spending-prediction": {"vi": "Dự Đoán Thu Chi", "en": "Spending Prediction"},
"category-report": {"vi": "Báo Cáo Thu Chi", "en": "Cashflow Report"},
"cashflow-overview": {"vi": "Báo Cáo Thu Chi Tổng Hợp", "en": "Cashflow Overview"},
"asset-tracker": {"vi": "Báo Cáo Tổng Tài Sản", "en": "Asset Tracker"},
"tags": {"vi": "Gắn Nhãn Giao Dịch", "en": "Transaction Tags"},
"setting-budget": {"vi": "Giới Hạn Chi Tiêu", "en": "Setting Budgets"},
"split-bill": {"vi": "Chia Tiền Nhóm", "en": "Split Bill"},
"schedule-payment": {"vi": "Chuyển Tiền Theo Lịch", "en": "Scheduled Payment"},
"term-deposit": {"vi": "Tiết Kiệm Trực Tuyến", "en": "Term Deposit"},
"overdraft": {"vi": "Thấu Chi", "en": "Overdraft"},
"installment-loan": {"vi": "Vay Trả Góp", "en": "Installment Loan"},
"visa-credit-card": {"vi": "Thẻ Tín Dụng VISA", "en": "VISA Credit Card"},
"vinacapital-investments": {"vi": "Quỹ VCAM/VinaCapital", "en": "VCAM/VinaCapital Investments"},
# "vcam-investment": {"vi": "Quỹ VCAM", "en": "VCAM Investment"},
"present": {"vi": "Lì Xì", "en": "Lixi"},
"liberty-travel-insurance": {"vi": "Bảo Hiểm Du Lịch", "en": "Travel Insurance"},