-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathastrology_app_test.py
1629 lines (1363 loc) · 68.7 KB
/
astrology_app_test.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
import geopy.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
# Đườ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
def get_city_suggestions(query):
geolocator = Nominatim(user_agent="astrology_app")
location = geolocator.geocode(query, exactly_one=False, limit=5, language='en') # Thêm tham số 'language'
if location:
return [f"{loc.address} ({loc.latitude}, {loc.longitude})" for loc in location]
return []
# 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 get latitude, longitude, and timezone from place name
def get_location_and_timezone(place):
geolocator = geopy.geocoders.Nominatim(user_agent="astrology_app")
location = geolocator.geocode(place)
if location:
lat, lon = location.latitude, location.longitude
tf = TimezoneFinder()
timezone = tf.timezone_at(lat=lat, lng=lon)
return lat, lon, timezone
else:
st.error(f"Cannot find location for place: {place}")
return None #10.8231, 106.6297, 'Asia/Ho_Chi_Minh' # Default to Ho Chi Minh
# 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', 'Convenience', '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', 'Convenience', '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----------------------------------
# Đị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):
traits = list(final_scores.keys())
scores = [final_scores[trait] for trait in traits]
avg_scores = [average_scores[trait] for trait in traits]
# 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
hover_texts_avg = [f"Score: {score:.2f}<br>Level: {get_score_level(score)}" for score in avg_scores]
hover_texts_user = [f"Score: <b>{score:.2f}</b><br>Level: <b>{get_score_level(score, language)}</b>" for score in 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',
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='<span style="color:black;">Your Trait</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 = [ 'blue', 'purple','red', 'orange', '#f1d800','green']
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-size:16px;">{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 = "English"
# 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")
# 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=500):
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 []
# Hàm lưu (append) một báo cáo cụ thể vào file txt
def append_report_cache_to_txt(user_hash, financial_traits_text, top_traits_description):
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') # Chuyển \n thành \\n
top_traits_description = top_traits_description.replace('\n', '\\n') # Chuyển \n thành \\n
file.write(f"{user_hash}|{financial_traits_text}|{top_traits_description}\n")
# Hàm khôi phục report_cache từ file txt
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('|', 2)
if len(parts) == 3:
user_hash = parts[0]
financial_traits_text = parts[1].replace('\\n', '\n') # Chuyển \\n thành \n
top_traits_description = parts[2].replace('\\n', '\n') # Chuyển \\n thành \n
report_cache[user_hash] = (financial_traits_text, top_traits_description)
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
# -----------------HỆ THỐNG ĐIỂM PRODUCT-----------------------------------------------------------------------
# Hàm để đọc điều kiện từ file CSV và chuyển thành hàm lambda
def load_conditions_from_csv(file_path):
conditions_df = pd.read_csv(file_path)
conditions_dict = {}
for _, row in conditions_df.iterrows():
# Sử dụng `eval` để chuyển chuỗi điều kiện thành hàm lambda thực thi được
conditions_dict[row['Product']] = eval(f"lambda scores: {row['Condition']}")
return conditions_dict
# Đọc product_conditions từ file CSV
product_conditions = load_conditions_from_csv('product_conditions.csv')
# Đọc necessity_rules từ file CSV
necessity_rules = load_conditions_from_csv('necessity_rules.csv')
# RandomForest function for model-based eligibility checking
def evaluate_product_with_model(clf, final_scores):
features = np.array(list(final_scores.values())).reshape(1, -1)
prediction = clf.predict(features)
return prediction[0]
# Check eligibility for products
def check_product_eligibility(product, final_scores, clf=None):
if clf:
return evaluate_product_with_model(clf, final_scores)
if product in product_conditions:
return product_conditions[product](final_scores)
return True
# Check necessity for products
def evaluate_product_necessity(final_scores, product, clf=None):
if clf:
return evaluate_product_with_model(clf, final_scores)
if product in necessity_rules:
return necessity_rules[product](final_scores)
return False
# Calculate product scores using traits and weights
def calculate_product_scores_numpy(final_scores, product_keywords, keyword_to_trait_mapping, weight_factor=1.0):
trait_names = list(final_scores.keys())
trait_scores = np.array([final_scores[trait] for trait in trait_names])
product_scores = {}
for product, keywords in product_keywords.items():
score = 0
for keyword in keywords:
if keyword in keyword_to_trait_mapping:
keyword_trait_weights = np.array([keyword_to_trait_mapping[keyword].get(trait, 0) for trait in trait_names])
score += weight_factor * np.dot(trait_scores, keyword_trait_weights)
# Sửa lại để trả về một dictionary chứa 'Score', 'Eligible', 'Necessary'
product_scores[product] = {
'Score': score,
'Eligible': check_product_eligibility(product, final_scores),
'Necessary': evaluate_product_necessity(final_scores, product)
}
return product_scores
# Hàm để tìm số cụm tối ưu dựa trên Silhouette Score
def find_optimal_clusters(scores, max_clusters=10):
# DÙng silhouette_scores
silhouette_scores = []
for n_clusters in range(3, max_clusters + 1):
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
labels = kmeans.fit_predict(scores)
score = silhouette_score(scores, labels)
silhouette_scores.append((n_clusters, score))
# Tìm n_clusters có silhouette score cao nhất
optimal_n_clusters = max(silhouette_scores, key=lambda x: x[1])[0]
print(f"Number of clusters: {optimal_n_clusters}")
return optimal_n_clusters
# Cập nhật hàm gán nhãn bằng KMeans clustering
def assign_labels_using_kmeans(product_scores, max_clusters=10, random_state=42):
labeled_scores = {}
# Kiểm tra 'Product_Score'
# print(product_scores)
# Sử dụng get để đảm bảo có thể lấy được 'Score' hoặc giá trị mặc định là 0 nếu không có
scores = np.array([result.get('Score', 0) for result in product_scores.values()]).reshape(-1, 1)
# Tìm số cụm tối ưu dựa trên điểm số
optimal_n_clusters = find_optimal_clusters(scores, max_clusters)
# Áp dụng KMeans
kmeans = KMeans(n_clusters=optimal_n_clusters, random_state=random_state)
labels = kmeans.fit_predict(scores)
for i, (product, result) in enumerate(product_scores.items()):
score = result.get('Score', 0) # Đảm bảo 'Score' luôn tồn tại
eligible = result.get('Eligible', True) # Giá trị mặc định là True
necessary = result.get('Necessary', False) # Giá trị mặc định là False
# Gán nhãn dựa trên giá trị của ngôn ngữ
if language == "Tiếng Việt":
label_names = ["Rất phù hợp", "Phù hợp", "Ít quan tâm", "Có thể không quan tâm"]
else:
label_names = ["Very Suitable", "Suitable", "Little interest", "Might not be interested"]
label = labels[i]
label_name = label_names[label] if label < len(label_names) else label_names[-1]
if eligible and label_name == label_names[-1] and score > 68:
label_name = label_names[0] # "Eligible" hoặc "Hợp lệ"
if necessary:
label_name = f"Necessary - {label_name}" if language == "English" else f"Cần thiết - {label_name}"
labeled_scores[product] = {'Score': score, 'Eligible': eligible, 'Necessary': necessary, 'Label': label_name}
return labeled_scores
# Get final product scores (combines eligibility, necessity, and scoring)
def get_final_product_scores(final_scores, product_keywords, keyword_to_trait_mapping, clf=None, language="Tiếng Việt"):
product_scores = calculate_product_scores_numpy(final_scores, product_keywords, keyword_to_trait_mapping)
product_info = {}
for product, score in product_scores.items():
is_eligible = check_product_eligibility(product, final_scores, clf)
is_necessary = evaluate_product_necessity(final_scores, product, clf)
product_info[product] = {
'Score': score,
'Eligible': is_eligible,
'Necessary': is_necessary
}
# Điều chỉnh gán nhãn theo ngôn ngữ
product_info = assign_labels_using_kmeans(product_info, language=language)
return product_info
#--------------------------------RATE APP-----------------------------------------------
# Hàm lưu đánh giá vào file JSON
def save_feedback(stars, comment, language):
feedback_data = {
"rating": stars,
"comment": comment,
"language": language,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# Tạo file nếu chưa có, hoặc ghi vào file có sẵn
try:
with open("feedback.json", "r+") as file:
data = json.load(file)
data.append(feedback_data)
file.seek(0)
json.dump(data, file, indent=4)
except FileNotFoundError:
with open("feedback.json", "w") as file:
json.dump([feedback_data], file, indent=4)
# Lưu trạng thái khi nhấn nút Calculate bằng session state
if "calculate_pressed" not in st.session_state:
st.session_state.calculate_pressed = False
# ----------------------------Streamlit UI---------------------------------------------
# Streamlit UI
st.markdown(
"""
<style>
.emoji {
font-size: 50px;
text-align: center;
}
.title {
font-size: 50px;
color: #6A0DAD;
text-align: center;
display: inline-block;
background: linear-gradient(90deg, rgba(106,13,173,1) 0%, rgba(163,43,237,1) 50%, rgba(252,176,69,1) 100%);
-webkit-background-clip: text;
color: transparent;
margin: 0 10px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
# margin-bottom: 100px; /* Điều chỉnh khoảng cách giữa tiêu đề và tab */
}
/* Điều chỉnh khoảng cách tab */
.stTabs {
margin-top: -60px; /* Điều chỉnh khoảng cách giữa tiêu đề và tab */
}
</style>
<div class="container">
<div class="emoji">✨</div>
<div class="title">ASTROTOMI</div>
<div class="emoji">✨</div>
</div>
""",
unsafe_allow_html=True
)
st.markdown(
"""
<style>
/* Tạo lớp phủ nền riêng biệt */
.bg-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('https://chiemtinhlaso.com/assets/images/hand_bg.png');
background-size: contain;
background-position: center; /* Đảm bảo hình nền căn giữa */