File size: 91,970 Bytes
d56a3b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739739a
d56a3b1
 
 
739739a
d56a3b1
 
 
 
 
 
739739a
4c30178
d56a3b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f1d8b4
 
 
d56a3b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c06101c
d56a3b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


# PHẦN 1: CÀI ĐẶT THƯ VIỆN
# ============================================================

import os
import sys
import warnings
warnings.filterwarnings('ignore')

print('='*70)
print('🏛️ AI HÀNH CHÍNH CÔNG VIP - CÀI ĐẶT THƯ VIỆN')
print('='*70)
print()

# Tạo thư mục (đường dẫn tương đối cho Hugging Face Spaces)
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
os.makedirs(os.path.join(DATA_DIR, 'data'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'models'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'audio_output'), exist_ok=True)

# Cài đặt thư viện - tự động cài đặt từ requirements.txt trên Hugging Face Spaces
print('📦 Thư viện sẽ được cài đặt tự động từ requirements.txt')
print('✅ Sẵn sàng!')
print()
ADMIN_PASSWORD = 'TOIDEPTRAI'

# Import libraries
import torch
import gc
import json
import numpy as np
import pandas as pd
from datetime import datetime

# Check GPU
print('🔍 Kiểm tra GPU:')
print(f'   - CUDA Available: {torch.cuda.is_available()}')
if torch.cuda.is_available():
    print(f'   - GPU Name: {torch.cuda.get_device_name(0)}')
    print(f'   - GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB')
    # Set memory efficient
    torch.cuda.empty_cache()
else:
    print('   - ⚠️ Không có GPU, sẽ chạy bằng CPU (chậm hơn)')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 2: TẢI DATASET
# ============================================================

print('='*70)
print('📥 TẢI DATASET')
print('='*70)
print()

# Dataset: Thủ tục hành chính
try:
    from datasets import load_dataset
    print('🔄 Đang tải Dataset: Thủ tục hành chính...')
    ds1 = load_dataset('hirine/dataset-thu-tuc-hanh-chinh-5733-samples', split='train')
    df1 = pd.DataFrame(ds1)
    print(f'   ✅ Dataset: {len(df1):,} samples')
except Exception as e:
    print(f'   ⚠️ Lỗi tải Dataset: {e}')
    print('   🔄 Tạo dữ liệu mẫu...')
    df1 = pd.DataFrame({
        'id': range(100),
        'title': ['Khai sinh', 'Kết hôn', 'Làm CCCD', 'Tách hộ khẩu', 'Đổi bằng lái',
                  'Khai tử', 'Hộ chiếu', 'Lý lịch tư pháp', 'Sổ đỏ', 'Sang tên'] * 10,
        'text': ['Thủ tục hành chính cần giấy tờ...' * 5] * 100
    })
    print(f'   ✅ Đã tạo dữ liệu mẫu: {len(df1)} records')

df1.to_csv(os.path.join(DATA_DIR, 'data/ds1.csv'), index=False)
print()

# Tổng kết
print(f'📊 Tổng samples: {len(df1):,}')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 3: DATA DỊCH VỤ CÔNG (40+ THỦ TỤC)
# ============================================================

print('='*70)
print('📥 TẢI DATA DỊCH VỤ CÔNG')
print('='*70)
print()

# Dữ liệu Dịch vụ công đầy đủ
DICHVUCONG_DATA = {
     #  NHÂN SỰ - HỘ TỊCH - TƯ PHÁP
    "KHAI_SINH": {"code":"KHAI_SINH","name":"Đăng ký khai sinh trực tiếp","docs":"Giấy chứng sinh, CCCD cha mẹ, Giấy chứng nhận kết hôn","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Làm trong 60 ngày kể từ khi sinh","steps":"1. Đến UBND nơi cư trú. 2. Nộp hồ sơ. 3. Nhận kết quả.","category":"NHAN_SU"},
    "KHAISINH_LIEN_THONG": {"code":"KS_LT","name":"Liên thông: Khai sinh - Thường trú - Cấp thẻ BHYT","docs":"Giấy chứng sinh điện tử, CCCD cha mẹ","coquan":"Cổng Dịch vụ công Quốc gia","time":"1-3 ngày làm việc","lephi":"Miễn phí","note":"Thực hiện online 100%, nhận 3 kết quả cùng lúc","steps":"1. Truy cập dichvucong.gov.vn. 2. Chọn nhóm dịch vụ Liên thông. 3. Nhập mã giấy chứng sinh. 4. Chờ phê duyệt.","category":"NHAN_SU"},
    "KHAI_SINH_QUA_HAN": {"code":"KHAI_SINH_QUA_HAN","name":"Khai sinh quá hạn","docs":"Giấy chứng sinh, CCCD, Đơn giải trình lý do chậm trễ","coquan":"UBND xã/phường","time":"3-7 ngày","lephi":"Miễn phí","note":"Quá 60 ngày cần đơn giải trình","steps":"1. Viết đơn giải trình. 2. Xin xác nhận tổ dân phố (nếu cần). 3. Nộp hồ sơ tại UBND.","category":"NHAN_SU"},
    "KHAI_TU": {"code":"KHAI_TU","name":"Đăng ký khai tử","docs":"Giấy báo tử, CCCD người chết","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Làm trong 60 ngày kể từ khi chết","steps":"1. Lấy giấy báo tử tại Y tế/Công an. 2. Đến UBND nơi người chết cư trú.","category":"NHAN_SU"},
    "KET_HON": {"code":"KET_HON","name":"Đăng ký kết hôn (Trong nước)","docs":"CCCD cả 2 bên, Giấy xác nhận tình trạng hôn nhân (nếu cư trú khác nơi đăng ký)","coquan":"UBND xã/phường","time":"1-3 ngày","lephi":"Miễn phí","note":"Cả 2 bên phải có mặt","steps":"1. Chuẩn bị giấy xác nhận độc thân. 2. Cả 2 cùng đến UBND ký vào sổ hộ tịch.","category":"NHAN_SU"},
    "LY_HON": {"code":"LY_HON","name":"Ly hôn thuận tình","docs":"Đơn xin ly hôn, Giấy kết hôn gốc, CCCD, Khai sinh con (nếu có)","coquan":"Tòa án nhân dân quận/huyện","time":"2-4 tháng","lephi":"300.000đ","note":"Cả hai bên cùng đồng ý và ký tên","steps":"1. Nộp đơn tại Tòa án. 2. Hòa giải. 3. Tòa án ra quyết định công nhận.","category":"NHAN_SU"},
    "KET_HON_NGOAI": {"code":"KET_HON_NGOAI","name":"Kết hôn có yếu tố nước ngoài","docs":"Hộ chiếu, Giấy xác nhận tình trạng hôn nhân hợp pháp hóa lãnh sự","coquan":"UBND quận/huyện","time":"5-15 ngày","lephi":"Dưới 1.000.000đ","note":"Cần phỏng vấn tại tư pháp","steps":"1. Hợp pháp hóa giấy tờ nước ngoài. 2. Nộp tại UBND quận. 3. Phỏng vấn và ký giấy.","category":"NHAN_SU"},
    "SAO_KHAI_SINH": {"code":"SAO_KS","name":"Cấp bản sao trích lục khai sinh","docs":"CCCD người yêu cầu","coquan":"UBND nơi lưu trữ sổ gốc","time":"Ngay trong ngày","lephi":"8.000đ/bản","note":"Có thể xin bản sao điện tử trên DVC","steps":"1. Nộp tờ khai. 2. Đóng phí. 3. Nhận bản sao trích lục.","category":"NHAN_SU"},
    "XAC_NHAN_DOC_THAN": {"code":"XN_DOC_THAN","name":"Xác nhận tình trạng hôn nhân (Độc thân)","docs":"CCCD, Quyết định ly hôn (nếu có)","coquan":"UBND xã/phường nơi thường trú","time":"1-3 ngày","lephi":"15.000đ","note":"Giấy có giá trị 6 tháng để kết hôn hoặc vay vốn","steps":"1. Nộp đơn tại UBND. 2. Cán bộ kiểm tra sổ hộ tịch. 3. Cấp giấy.","category":"NHAN_SU"},
    "LY_LICH_TU_PHAP": {"code":"LLTP","name":"Cấp lý lịch tư pháp (Số 1 và Số 2)","docs":"CCCD gắn chip","coquan":"Sở Tư pháp hoặc qua app VNeID","time":"7-10 ngày","lephi":"200.000đ (miễn phí cho một số đối tượng)","note":"Hiện tại làm qua VNeID rất nhanh","steps":"1. Truy cập VNeID hoặc DVC. 2. Chọn cấp LLTP. 3. Thanh toán online. 4. Nhận kết quả bản điện tử/giấy.","category":"HANH_CHINH"},
    "CHUNG_THUC_BAN_SAO": {"code":"CHUNG_THUC","name":"Công chứng/Chứng thực bản sao (Sao y bản chính)","docs":"Bản chính giấy tờ, Bản photo","coquan":"UBND xã/phường hoặc Phòng Công chứng","time":"Lấy ngay","lephi":"2.000đ/trang","note":"Trình bản chính để đối chiếu","steps":"1. Xuất trình bản chính. 2. Cán bộ ký đóng dấu vào bản photo.","category":"HANH_CHINH"},
    # CCCD - HỘ KHẨU 
    "CCCD_CAP": {"code":"CCCD_CAP","name":"Cấp CCCD lần đầu","docs":"Giấy khai sinh, Sổ hộ khẩu","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Từ 14 tuổi","steps":"1. Chuẩn bị giấy khai sinh, sổ hộ khẩu. 2. Đến Công an quận/huyện. 3. Làm thủ tục cấp CCCD.","category":"CCCD_HK"},
    "CCCD_DOI": {"code":"CCCD_DOI","name":"Đổi CCCD","docs":"CCCD cũ, Giấy khai sinh","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Đổi khi hư hỏng/sai thông tin","steps":"1. Mang CCCD cũ. 2. Đến Công an nơi thường trú. 3. Làm thủ tục đổi.","category":"CCCD_HK"},
    "VNEID_DINH_DANH_2": {"code":"VNEID_L2","name":"Đăng ký tài khoản VNeID Mức độ 2","docs":"CCCD gắn chip, Số điện thoại chính chủ","coquan":"Công an xã/phường","time":"3-5 ngày duyệt","lephi":"Miễn phí","note":"Thay thế thẻ vật lý khi đi máy bay, hành chính","steps":"1. Đến trực tiếp Công an. 2. Quét chip CCCD. 3. Chụp ảnh chân dung. 4. Chờ Bộ Công an phê duyệt.","category":"CCCD_HK"},
    "CCCD_CAP_LAI": {"code":"CCCD_CAP_LAI","name":"Cấp lại CCCD khi mất","docs":"Giấy khai sinh, Sổ hộ khẩu, Đơn xin cấp lại","coquan":"Công an quận/huyện","time":"7-15 ngày","lephi":"Miễn phí","note":"Báo mất và xin cấp lại","steps":"1. Làm đơn báo mất. 2. Đến Công an. 3. Xin cấp lại CCCD.","category":"CCCD_HK"},
    "HO_KHAU_TACH": {"code":"HO_KHAU_TACH","name":"Tách sổ hộ khẩu","docs":"Sổ hộ khẩu, CCCD, Đơn xin tách, Giấy tờ nơi ở mới","coquan":"Công an quận/huyện","time":"3-7 ngày","lephi":"Miễn phí","note":"Cần đồng ý chủ hộ","steps":"1. Viết đơn xin tách hộ khẩu. 2. Xin chữ ký chủ hộ. 3. Đến Công án làm thủ tục.","category":"CCCD_HK"},
    "HO_KHAU_NHAP": {"code":"HO_KHAU_NHAP","name":"Nhập sổ hộ khẩu","docs":"Sổ hộ khẩu, CCCD, Đơn xin nhập, Giấy tờ nơi ở","coquan":"Công an quận/huyện","time":"3-7 ngày","lephi":"Miễn phí","note":"Cần đồng ý chủ hộ mới","steps":"1. Viết đơn xin nhập. 2. Xin đồng ý chủ hộ mới. 3. Đến Công an làm thủ tục.","category":"CCCD_HK"},
    "TAM_TRU": {"code":"TAM_TRU","name":"Đăng ký tạm trú","docs":"CCCD, Đơn xin tạm trú, Giấy tờ nơi ở","coquan":"Công an xã/phường","time":"3-5 ngày","lephi":"Miễn phí","note":"Tối đa 2 năm","steps":"1. Lấy giấy xác nhận của chủ nhà. 2. Viết đơn xin tạm trú. 3. Đến Công an xã/phường.","category":"CCCD_HK"},
    "TRU_SO": {"code":"TRU_SO","name":"Đăng ký trú quán","docs":"CCCD, Đơn xin trú quán, Xác nhận tổ dân phố","coquan":"Công an xã/phường","time":"3-5 ngày","lephi":"Miễn phí","note":"Cho người không có hộ khẩu","steps":"1. Xin xác nhận tổ dân phố. 2. Viết đơn xin trú quán. 3. Đến Công an.","category":"CCCD_HK"},
    "TAM_VANG": {"code":"TAM_VANG","name":"Đăng ký tạm vắng","docs":"CCCD, Sổ hộ khẩu, Đơn xin","coquan":"Công an","time":"1-3 ngày","lephi":"Miễn phí","note":"Khi đi khỏi nơi thường trú trên 30 ngày","steps":"1. Làm đơn xin báo tạm vắng. 2. Nộp tại Công an nơi thường trú.","category":"CCCD_HK"},

    # BẰNG LÁI 
    "BANG_LAI_A1": {"code":"BANG_LAI_A1","name":"Cấp đổi bằng lái A1","docs":"CCCD, Giấy khai sinh (nếu dưới 18), 1 ảnh 3x4","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"Miễn phí","note":"Từ 18 tuổi, đổi bằng không cần thi lại","steps":"1. Chuẩn bị CCCD, ảnh. 2. Đến Sở GTVT hoặc trung tâm sát hạch. 3. Nộp hồ sơ và nhận bằng mới.","category":"GIAO_THONG"},
    "BANG_LAI_A1_CAP_MOI": {"code":"BANG_LAI_A1_CAP_MOI","name":"Cấp mới bằng lái A1","docs":"CCCD, Giấy khai sinh, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"15-20 ngày","lephi":"110.000đ","note":"Phải thi sát hạch","steps":"1. Học lý thuyết và sa hình. 2. Đăng ký thi. 3. Thi sát hạch. 4. Nhận bằng khi đỗ.","category":"GIAO_THONG"},
    "BANG_LAI_A2": {"code":"BANG_LAI_A2","name":"Cấp bằng lái A2","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"15-20 ngày","lephi":"150.000đ","note":"Xe mô tô trên 175cc","steps":"1. Học lý thuyết và sa hình A2. 2. Đăng ký thi. 3. Thi và nhận bằng.","category":"GIAO_THONG"},
    "BANG_LAI_B1": {"code":"BANG_LAI_B1","name":"Cấp bằng lái B1","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"20-25 ngày","lephi":"350.000đ","note":"Ô tô dưới 9 chỗ","steps":"1. Học lý thuyết, sa hình, đường trường. 2. Đăng ký thi B1. 3. Thi và nhận bằng.","category":"GIAO_THONG"},
    "BANG_LAI_B2": {"code":"BANG_LAI_B2","name":"Cấp bằng lái B2","docs":"CCCD, Học lý, Giấy khám sức khỏe","coquan":"Sở GTVT","time":"20-25 ngày","lephi":"400.000đ","note":"Ô tô kinh doanh","steps":"1. Học lý thuyết, sa hình, đường trường. 2. Đăng ký thi B2. 3. Thi và nhận bằng.","category":"GIAO_THONG"},
    "BANG_LAI_DOI": {"code":"BANG_LAI_DOI","name":"Đổi bằng lái","docs":"GPLX cũ, CCCD, 1 ảnh 3x4","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"135.000đ","note":"Đổi khi hết hạn","steps":"1. Chuẩn bị bằng cũ, CCCD, ảnh. 2. Đến Sở GTVT. 3. Đổi bằng mới.","category":"GIAO_THONG"},
    "BANG_LAI_CAP_LAI": {"code":"BANG_LAI_CAP_LAI","name":"Cấp lại bằng lái","docs":"CCCD, 1 ảnh 3x4, Đơn xin cấp lại","coquan":"Sở GTVT","time":"7-10 ngày","lephi":"135.000đ","note":"Khi bị mất","steps":"1. Làm đơn xin cấp lại. 2. Đến Sở GTVT. 3. Nhận bằng mới.","category":"GIAO_THONG"},
    "GPLX_ONLINE": {"code":"GPLX_ONL","name":"Đổi Giấy phép lái xe trực tuyến (Mức độ 4)","docs":"GPLX cũ, Giấy khám sức khỏe điện tử, Ảnh chân dung file số","coquan":"Cổng DVC Cục đường bộ","time":"5 ngày","lephi":"115.000đ","note":"Nhận bằng tại nhà qua bưu điện","steps":"1. Khám sức khỏe tại BV có kết nối dữ liệu. 2. Kê khai trên DVC. 3. Thanh toán tiền. 4. Bưu điện giao bằng.","category":"GIAO_THONG"},
    "DANG_KY_XE": {"code":"DK_XE","name":"Đăng ký xe máy/ô tô mới","docs":"Hóa đơn GTGT, Tờ khai lệ phí trước bạ, Giấy xuất xưởng","coquan":"Công an quận/huyện","time":"1 ngày","lephi":"Tùy loại xe và khu vực","note":"Phải đóng thuế trước bạ trước khi đăng ký","steps":"1. Đóng thuế trước bạ. 2. Đến công an bấm biển số. 3. Nhận biển số ngay, nhận cavet sau 2 ngày.","category":"GIAO_THONG"},
    "THU_HOI_BIEN_SO": {"code":"THU_HOI","name":"Thu hồi biển số, đăng ký xe (Khi bán xe)","docs":"Cavet xe, Biển số xe, Hợp đồng mua bán","coquan":"Cơ quan Công an nơi quản lý xe","time":"2 ngày","lephi":"Miễn phí","note":"Biển số sẽ được giữ lại làm biển định danh cho chủ cũ","steps":"1. Chủ xe nộp đăng ký và biển số. 2. Nhận giấy chứng nhận thu hồi.","category":"GIAO_THONG"},
    "PHAT_NGUOI": {"code":"PHAT_NGUOI","name":"Nộp phạt vi phạm giao thông (Phạt nguội)","docs":"Mã số quyết định xử phạt","coquan":"Cổng Dịch vụ công Quốc gia","time":"Xử lý ngay","lephi":"Theo biên bản","note":"Có thể tra cứu bằng biển số xe","steps":"1. Tra cứu mã vi phạm. 2. Thanh toán qua ngân hàng/ví điện tử. 3. Hệ thống tự động xóa lỗi.","category":"GIAO_THONG"},
    "DANG_KIEM": {"code":"DANG_KIEM","name":"Đăng kiểm xe cơ giới","docs":"Cavet xe, Đăng kiểm cũ, Bảo hiểm dân sự","coquan":"Trạm đăng kiểm","time":"1-2 giờ","lephi":"~300.000đ + Phí đường bộ","note":"Nên đặt lịch qua app TTDK","steps":"1. Đặt lịch. 2. Đưa xe đến kiểm định. 3. Dán tem mới.","category":"GIAO_THONG"},
    # HỘ CHIẾU (2)
    "HO_CHIEU_CAP": {"code":"HO_CHIEU_CAP","name":"Cấp hộ chiếu","docs":"CCCD, Sổ hộ khẩu, 4 ảnh 4x6","coquan":"Phòng xuất nhập cảnh","time":"7-15 ngày","lephi":"200.000đ","note":"Hộ chiếu gắn chip 10 năm","steps":"1. Chuẩn bị CCCD, hộ khẩu, ảnh. 2. Làm đơn xin cấp hộ chiếu. 3. Nộp tại Phòng XNC. 4. Nhận hộ chiếu.","category":"HANH_CHINH"},
    "HO_CHIEU_GIA_HAN": {"code":"HO_CHIEU_GIA_HAN","name":"Gia hạn hộ chiếu","docs":"Hộ chiếu cũ, CCCD, 1 ảnh 4x6","coquan":"Phòng xuất nhập cảnh","time":"5-10 ngày","lephi":"200.000đ","note":"Trước 6 tháng hết hạn","steps":"1. Kiểm tra hạn hộ chiếu. 2. Chuẩn bị hồ sơ gia hạn. 3. Nộp tại Phòng XNC.","category":"HANH_CHINH"},

    # LÝ LỊCH (1)
    "LY_LICH_TU_PHAP": {"code":"LLTP","name":"Cấp lý lịch tư pháp","docs":"CCCD, Đơn xin cấp LLTP","coquan":"Sở Tư pháp","time":"3-5 ngày","lephi":"Miễn phí","note":"Kiểm tra án tích","steps":"1. Làm đơn xin cấp lý lịch tư pháp. 2. Nộp tại Sở Tư pháp. 3. Chờ 3-5 ngày và nhận kết quả.","category":"HANH_CHINH"},

    # NHÀ ĐẤT (2)
    "SO_DO": {"code":"SO_DO","name":"Cấp Sổ đỏ lần đầu","docs":"Giấy tờ chứng minh quyền sử dụng đất, Trích lục bản đồ","coquan":"Văn phòng đăng ký đất đai","time":"20-30 ngày","lephi":"Tiền sử dụng đất + Lệ phí cấp sổ","note":"Quy trình phức tạp, cần xác minh nguồn gốc đất","steps":"1. Nộp hồ sơ tại bộ phận 1 cửa. 2. Niêm yết công khai. 3. Thực hiện nghĩa vụ tài chính. 4. Nhận sổ.","category":"NHADAT"},
    "SANG_TEN": {"code":"SANG_TEN","name":"Sang tên Sổ đỏ (Chuyển nhượng nhà đất)","docs":"Hợp đồng công chứng, Sổ đỏ gốc, CCCD hai bên","coquan":"Văn phòng đăng ký đất đai","time":"10-15 ngày","lephi":"Thuế TNCN 2%, Lệ phí trước bạ 0.5%","note":"Phải công chứng hợp đồng trước","steps":"1. Ký hợp đồng tại phòng công chứng. 2. Nộp hồ sơ đăng ký biến động. 3. Đóng thuế. 4. Nhận sổ đã sang tên.","category":"NHADAT"},
    "QUY_HOACH_DAT": {"code":"QH_DAT","name":"Tra cứu thông tin quy hoạch đất đai","docs":"Phiếu yêu cầu, CCCD","coquan":"Phòng Tài nguyên và Môi trường","time":"2 ngày","lephi":"Theo quy định","note":"Để biết đất có dính dự án, lộ giới không","steps":"1. Gửi yêu cầu. 2. Cơ quan chức năng cung cấp văn bản trả lời.","category":"NHADAT"},
    "CAP_PHEP_XAY_DUNG": {"code":"PHEP_XD","name":"Cấp phép xây dựng nhà ở riêng lẻ","docs":"Bản vẽ thiết kế, Sổ đỏ sao y, Đơn xin cấp phép","coquan":"UBND quận/huyện","time":"15 ngày","lephi":"~100.000đ","note":"Bắt buộc phải có trước khi khởi công","steps":"1. Thuê đơn vị thiết kế bản vẽ. 2. Nộp hồ sơ tại Quận. 3. Cán bộ kiểm tra thực địa. 4. Cấp phép.","category":"XAYDUNG"},

    # GIAO THÔNG XE (2)
    "DANG_KY_XE": {"code":"DANG_KY_XE","name":"Đăng ký xe ô tô","docs":"CCCD, Giấy chứng nhận quyền sở hữu xe, Giấy bảo hiểm","coquan":"Phòng đăng ký xe","time":"1-3 ngày","lephi":"1.500.000đ-3.000.000đ","note":"Trong 30 ngày kể từ khi mua","steps":"1. Chuẩn bị giấy tờ xe. 2. Mua bảo hiểm xe. 3. Đến Phòng đăng ký xe. 4. Nộp hồ sơ và nhận biển số.","category":"GIAO_THONG"},
    "CHUYEN_NHUONG_XE": {"code":"CHUYEN_NHUONG_XE","name":"Sang tên chủ sở hữu xe","docs":"CCCD cả 2 bên, Sổ đăng ký xe, Hợp đồng chuyển nhượng","coquan":"Phòng đăng ký xe","time":"2-5 ngày","lephi":"500.000đ-1.000.000đ","note":"Cả 2 cùng đến làm","steps":"1. Làm hợp đồng chuyển nhượng có công chứng. 2. Cả 2 bên đến Phòng đăng ký xe. 3. Làm thủ tục chuyển nhượng.","category":"GIAO_THONG"},
    "SANG_TEN_XE_CUNG_TINH": {"code":"ST_CT","name":"Sang tên xe trong cùng tỉnh/thành phố","docs":"Hợp đồng mua bán công chứng, Chứng nhận thu hồi biển số, CCCD chủ mới","coquan":"Công an Quận/Huyện","time":"2-5 ngày","lephi":"Thuế trước bạ (1% - 2%) + phí đổi cavet","note":"Chủ cũ phải làm thủ tục thu hồi biển số trước","steps":"1. Công chứng hợp đồng mua bán. 2. Chủ cũ nộp biển số/cavet. 3. Chủ mới đóng thuế trước bạ. 4. Chủ mới đăng ký tại Công an.","category":"GIAO_THONG"},
    "SANG_TEN_XE_KHAC_TINH": {"code":"ST_KT","name":"Sang tên xe đi tỉnh khác (Di chuyển nguyên chủ)","docs":"Hồ sơ gốc của xe, Chứng nhận thu hồi, CCCD","coquan":"Công an nơi đến","time":"5-7 ngày","lephi":"Theo biểu phí khu vực đến","note":"Phải rút hồ sơ gốc (thu hồi) tại tỉnh cũ","steps":"1. Làm thủ tục thu hồi tại tỉnh cũ. 2. Cầm hồ sơ gốc đến Công an tỉnh mới. 3. Đóng thuế trước bạ tại nơi mới. 4. Bấm biển số mới.","category":"GIAO_THONG"},
    "DOI_MAU_SON_XE": {"code":"MAU_SON","name":"Đổi màu sơn xe (Thay đổi màu sắc xe)","docs":"Cavet xe, CCCD, Đơn đề nghị thay đổi màu sơn","coquan":"Phòng/Đội CSGT","time":"1-2 ngày","lephi":"30.000đ - 50.000đ","note":"Phải làm thủ tục TRƯỚC khi sơn lại xe","steps":"1. Mang xe đến CSGT. 2. Cán bộ kiểm tra xe hiện tại. 3. Viết đơn xin đổi màu sơn. 4. Đi sơn xe. 5. Đến cấp lại cavet mới với màu sơn mới.","category":"GIAO_THONG"},
    "CAP_LAI_BIEN_SO_MAT": {"code":"BS_MAT","name":"Cấp lại biển số xe bị mất/hỏng","docs":"CCCD, Cavet xe, Đơn trình báo mất biển số","coquan":"Phòng CSGT nơi đăng ký","time":"7 ngày làm việc","lephi":"150.000đ","note":"Biển số cấp lại vẫn là biển số định danh cũ của bạn","steps":"1. Nộp đơn báo mất tại Công an. 2. Kê khai trên DVC. 3. Chờ dập lại biển số mới theo số cũ.","category":"GIAO_THONG"},
    "DANG_KY_XE_MAY_DIEN": {"code":"XE_DIEN","name":"Đăng ký biển số xe máy điện","docs":"Hóa đơn GTGT, Phiếu kiểm tra chất lượng xuất xưởng, CCCD","coquan":"Công an xã/phường","time":"1 ngày","lephi":"Tùy khu vực (từ 50.000đ)","note":"Xe máy điện bắt buộc phải có biển số mới được lưu thông","steps":"1. Đóng thuế trước bạ. 2. Khai báo trên cổng DVC Bộ Công an. 3. Mang xe đến công an xã bấm biển.","category":"GIAO_THONG"},

    # XÂY DỰNG (1)
    "CAP_PHEP_XAY_DUNG": {"code":"PHEP_XD","name":"Cấp phép xây dựng nhà ở","docs":"Đơn xin, Giấy chứng nhận quyền sử dụng đất, Bản vẽ quy hoạch","coquan":"Phòng Đô thị","time":"15-20 ngày","lephi":"50.000đ-200.000đ","note":"Cần quy hoạch","steps":"1. Lấy bản vẽ quy hoạch 1/500. 2. Thiết kế bản vẽ xây dựng. 3. Làm đơn xin cấp phép. 4. Nộp tại Phòng Đô thị.","category":"XAYDUNG"},
    "THONG_BAO_KHOI_CONG": {"code": "KHOI_CONG", "name": "Thông báo khởi công xây dựng", "docs": "GPXD, Hợp đồng bảo hiểm, Thông tin nhà thầu", "coquan": "UBND Xã/Phường", "time": "3 ngày trước khi làm", "lephi": "Miễn phí", "note": "Bắt buộc phải báo trước khi đào móng", "steps": "1. Chuẩn bị GPXD. 2. Nộp đơn thông báo tại phường.", "category": "XAYDUNG"},
    "GIA_HAN_GPXD": {"code": "GH_GPXD", "name": "Gia hạn Giấy phép xây dựng", "docs": "Bản gốc GPXD, Đơn xin gia hạn", "coquan": "Cơ quan cấp phép cũ", "time": "5 ngày", "lephi": "15.000đ", "note": "Phải làm trước khi hết hạn 12 tháng", "steps": "1. Nộp đơn khi gần hết hạn. 2. Đóng dấu gia hạn vào bản gốc.", "category": "XAYDUNG"},
    "PHA_DO_CONG_TRINH": {"code": "PHA_DO", "name": "Cấp phép phá dỡ công trình", "docs": "Phương án phá dỡ, Đơn xin phá dỡ", "coquan": "UBND Quận/Huyện", "time": "7-10 ngày", "lephi": "Miễn phí", "note": "Đảm bảo an toàn cho các nhà liền kề", "steps": "1. Lập phương án che chắn. 2. Nộp thông báo phá dỡ.", "category": "XAYDUNG"},

    # KINH DOANH (1)
    "CAP_PHEP_KINH_DOANH": {"code":"PHEP_KD","name":"Cấp giấy phép kinh doanh","docs":"Đơn xin cấp phép kinh doanh, CCCD, Địa chỉ kinh doanh","coquan":"Phòng Đăng ký kinh doanh hoặc UBND","time":"3-5 ngày","lephi":"Miễn phí","note":"Kinh doanh cá thể miễn phí","steps":"1. Làm đơn xin cấp phép kinh doanh. 2. Chuẩn bị CCCD, địa chỉ kinh doanh. 3. Nộp tại Phòng ĐKKD hoặc UBND.","category":"KINHDOANH"},

    # Y TẾ (2)
    "BHYT_CAP_MOI": {"code": "BHYT_CAP", "name": "Đăng ký đóng, cấp thẻ BHYT đối với người chỉ tham gia BHYT", "docs": "CCCD, Tờ khai tham gia BHXH, BHYT (Mẫu TK1-TS)", "coquan": "Cơ quan Bảo hiểm xã hội / Đại lý thu", "time": "5 ngày làm việc", "lephi": "Theo mức đóng quy định", "note": "Đóng theo hộ gia đình sẽ được giảm trừ mức đóng từ người thứ 2", "steps": "1. Điền tờ khai Mẫu TK1-TS. 2. Đóng tiền cho Đại lý thu/Bưu điện. 3. Nhận thẻ BHYT mới.", "category": "YTE"},
    "BHYT_BHXH_DOI": {"code": "BHYT_DOI", "name": "Cấp lại, đổi, điều chỉnh thông tin trên sổ BHXH, thẻ BHYT", "docs": "Tờ khai (Mẫu TK1-TS), Sổ BHXH/Thẻ BHYT cũ, Giấy tờ minh chứng thay đổi", "coquan": "Bảo hiểm xã hội", "time": "Trong ngày (nếu không đổi thông tin) / 3-5 ngày (có đổi thông tin)", "lephi": "Miễn phí", "note": "Có thể thực hiện thủ tục trực tuyến qua ứng dụng VssID hoặc Cổng DVC", "steps": "1. Kê khai thông tin cần điều chỉnh. 2. Nộp hồ sơ kèm minh chứng. 3. Nhận thẻ/sổ mới.", "category": "YTE"},
    "BHYT_THANH_TOAN": {"code": "BHYT_TT", "name": "Thanh toán trực tiếp chi phí KCB BHYT / Cấp giấy chứng nhận không cùng chi trả", "docs": "Thẻ BHYT, CCCD, Hóa đơn viện phí gốc, Bản sao bệnh án/Giấy ra viện", "coquan": "Cơ quan BHXH cấp huyện/tỉnh", "time": "40 ngày", "lephi": "Miễn phí", "note": "Áp dụng lấy lại tiền khi khám trái tuyến hoặc cấp GCN miễn đồng chi trả (tham gia 5 năm liên tục)", "steps": "1. Tập hợp hóa đơn đỏ viện phí. 2. Nộp hồ sơ tại BHXH. 3. Nhận tiền thanh toán qua tài khoản.", "category": "YTE"},
    "BH_THAI_SAN": {"code": "THAI_SAN", "name": "Giải quyết hưởng chế độ thai sản", "docs": "Giấy chứng sinh/khai sinh của con, Giấy ra viện (nếu có)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6-10 ngày làm việc", "lephi": "Miễn phí", "note": "Cần đóng đủ 6 tháng BHXH trong vòng 12 tháng trước khi sinh", "steps": "1. Nộp giấy tờ sinh con cho Công ty. 2. Công ty lập danh sách nộp lên BHXH. 3. Nhận tiền thai sản.", "category": "YTE"},
    "BH_OM_DAU": {"code": "OM_DAU", "name": "Giải quyết hưởng chế độ ốm đau", "docs": "Giấy ra viện (nội trú) hoặc Giấy chứng nhận nghỉ việc hưởng BHXH (ngoại trú)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Phải nộp hồ sơ cho công ty trong vòng 45 ngày từ lúc đi làm lại", "steps": "1. Xin giấy nghỉ ốm theo chuẩn Bộ Y tế. 2. Nộp cho kế toán. 3. BHXH chuyển tiền trợ cấp.", "category": "YTE"},
    "BH_DSPHSK": {"code": "DSPHSK", "name": "Giải quyết hưởng trợ cấp nghỉ dưỡng sức phục hồi sức khỏe (DSPHSK)", "docs": "Danh sách đề nghị giải quyết hưởng chế độ (Mẫu 01B-HSB do công ty lập)", "coquan": "Cơ quan BHXH (qua Công ty)", "time": "6-10 ngày làm việc", "lephi": "Miễn phí", "note": "Dành cho NLĐ đã hết thời gian nghỉ ốm đau, thai sản mà sức khỏe còn yếu", "steps": "1. Công đoàn và Công ty xem xét số ngày nghỉ. 2. Công ty lập mẫu nộp BHXH. 3. Nhận tiền.", "category": "YTE"},
    "BH_TNLD_LAN1": {"code": "TNLD_L1", "name": "Giải quyết hưởng chế độ Tai nạn lao động, Bệnh nghề nghiệp (TNLĐ, BNN) lần đầu", "docs": "Biên bản điều tra TNLĐ, Giấy ra viện, Biên bản giám định tỷ lệ suy giảm KNLĐ", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Mức hưởng phụ thuộc vào tỷ lệ giám định suy giảm khả năng lao động", "steps": "1. Công ty lập biên bản tai nạn. 2. Đưa đi giám định y khoa. 3. Nộp hồ sơ nhận trợ cấp.", "category": "YTE"},
    "BH_TNLD_TIEP": {"code": "TNLD_TIEP", "name": "Giải quyết hưởng chế độ TNLĐ, BNN đối với trường hợp tiếp tục bị TNLĐ/BNN", "docs": "Hồ sơ vụ tai nạn mới, Biên bản giám định tổng hợp tỷ lệ suy giảm KNLĐ", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Áp dụng khi đang hưởng trợ cấp tai nạn cũ mà tiếp tục bị tai nạn mới", "steps": "1. Lập hồ sơ tai nạn mới. 2. Giám định tổng hợp phần trăm thương tật. 3. Nộp hồ sơ hưởng mức mới.", "category": "YTE"},
    "BH_TNLD_TAIPHAT": {"code": "TNLD_TP", "name": "Giải quyết chế độ TNLĐ, BNN do thương tật, bệnh tật tái phát", "docs": "Hồ sơ y tế điều trị tái phát, Biên bản giám định lại thương tật", "coquan": "Cơ quan BHXH", "time": "6 ngày làm việc", "lephi": "Miễn phí", "note": "Dành cho người đã hưởng TNLĐ nay bị đau lại do vết thương cũ", "steps": "1. Đi viện điều trị đợt tái phát. 2. Xin giám định lại tỷ lệ thương tật. 3. Nộp xin điều chỉnh trợ cấp.", "category": "YTE"},

    # GIÁO DỤC (1)
    "HOC_BONG": {"code":"HOC_BONG","name":"Đăng ký xét học bổng","docs":"Đơn xin học bổng, Bảng tổng kết, Học bạ","coquan":"Trường học hoặc Phòng GD-ĐT","time":"7-10 ngày","lephi":"Miễn phí","note":"Theo từng loại học bổng","steps":"1. Làm đơn xin xét học bổng. 2. Chuẩn bị hồ sơ (bảng tổng kết, học bạ). 3. Nộp tại trường hoặc Phòng GD-ĐT.","category":"GIAODUC"},
    "CN_BANG_DH_NN": {"code": "CN_BANG_DH", "name": "Công nhận bằng Cử nhân, Thạc sĩ, Tiến sĩ do nước ngoài cấp", "docs": "Bản sao văn bằng + bảng điểm, Bản dịch công chứng, Hộ chiếu/Minh chứng thời gian học ở nước ngoài", "coquan": "Cục Quản lý chất lượng (Bộ GD&ĐT)", "time": "20 ngày làm việc", "lephi": "500.000đ/văn bằng", "note": "Thực hiện trực tuyến 100% trên Cổng DVC Bộ GD&ĐT", "steps": "1. Tạo hồ sơ online. 2. Nộp lệ phí trực tuyến. 3. Cục thẩm định hồ sơ. 4. Nhận Giấy công nhận bản điện tử/giấy.", "category": "GIAODUC"},
    "CN_BANG_PT_NN": {"code": "CN_BANG_PT", "name": "Công nhận bằng tốt nghiệp THCS, THPT do nước ngoài cấp", "docs": "Bản sao văn bằng, Bảng điểm, Bản dịch công chứng, Giấy tờ tùy thân", "coquan": "Sở Giáo dục và Đào tạo địa phương", "time": "20 ngày làm việc", "lephi": "250.000đ/văn bằng", "note": "Dành cho học sinh học ở nước ngoài muốn về VN học tiếp hoặc xét tuyển", "steps": "1. Nộp hồ sơ trên cổng DVC của Sở. 2. Đóng phí. 3. Thẩm định. 4. Nhận Giấy công nhận.", "category": "GIAODUC"},
    "DAC_CACH_THPT": {"code": "DAC_CACH", "name": "Xét đặc cách tốt nghiệp THPT", "docs": "Đơn đề nghị, Hồ sơ nhập viện/Giấy ra viện (nếu ốm đau), Biên bản xác nhận của Hội đồng thi", "coquan": "Sở Giáo dục và Đào tạo", "time": "Chậm nhất 07 ngày sau buổi thi cuối cùng", "lephi": "Miễn phí", "note": "Chỉ áp dụng khi thí sinh bị tai nạn, ốm đau đột xuất trước hoặc trong kỳ thi", "steps": "1. Nộp đơn và hồ sơ bệnh án cho Trưởng Điểm thi/Trường THPT. 2. Trường nộp lên Sở GD&ĐT. 3. Sở ra quyết định đặc cách.", "category": "GIAODUC"},
    "PHUC_KHAO_THPT": {"code": "PHUC_KHAO", "name": "Phúc khảo bài thi tốt nghiệp THPT", "docs": "Đơn đề nghị phúc khảo bài thi (theo mẫu)", "coquan": "Nơi đăng ký dự thi (Trường THPT hoặc Phòng GD&ĐT)", "time": "15 ngày kể từ ngày hết hạn nhận đơn", "lephi": "Miễn phí (hoặc theo quy định của Sở)", "note": "Thí sinh phải nộp đơn trong vòng 10 ngày kể từ khi công bố điểm thi", "steps": "1. Viết đơn nộp tại trường cấp 3. 2. Trường chuyển dữ liệu lên Sở. 3. Tổ chức chấm lại. 4. Công bố điểm phúc khảo.", "category": "GIAODUC"},
    "XET_TN_THAC_SI": {"code": "TN_THS", "name": "Xét tốt nghiệp và cấp bằng Thạc sĩ", "docs": "Đơn xin xét tốt nghiệp, Bảng điểm toàn khóa, Chứng chỉ ngoại ngữ, Quyết định bảo vệ luận văn", "coquan": "Phòng Đào tạo Sau đại học (Cơ sở GD Đại học)", "time": "Theo kế hoạch xét tốt nghiệp của trường", "lephi": "Lệ phí cấp bằng (tùy trường)", "note": "Học viên phải nộp lưu chiểu luận văn trước khi xét", "steps": "1. Nộp đơn xin xét tốt nghiệp. 2. Hội đồng trường họp xét duyệt. 3. Hiệu trưởng ký quyết định. 4. Cấp bằng và bảng điểm.", "category": "GIAODUC"},
    "XET_CAP_TIEN_SI": {"code": "CAP_TS", "name": "Xét cấp bằng Tiến sĩ", "docs": "Hồ sơ bảo vệ luận án cấp viện/trường, Giấy biên nhận nộp lưu chiểu Thư viện Quốc gia", "coquan": "Cơ sở GD Đại học / Viện nghiên cứu", "time": "Theo đợt xét tốt nghiệp", "lephi": "Lệ phí cấp bằng (tùy trường)", "note": "Bắt buộc phải nộp bản cứng luận án vào Thư viện Quốc gia Việt Nam", "steps": "1. Hoàn thiện hồ sơ sau khi bảo vệ thành công. 2. Nộp lưu chiểu. 3. Hội đồng chức danh xét duyệt. 4. Cấp bằng.", "category": "GIAODUC"},
    "TUYEN_SINH_10": {"code": "TS_10", "name": "Tuyển sinh vào lớp 10 trung học phổ thông", "docs": "Đơn dự tuyển, Học bạ THCS, Giấy CN tốt nghiệp THCS tạm thời, Giấy chứng nhận ưu tiên (nếu có)", "coquan": "Sở Giáo dục và Đào tạo / Trường THPT", "time": "Theo lịch tuyển sinh của tỉnh/thành phố", "lephi": "Lệ phí thi (khoảng 150.000đ - 300.000đ)", "note": "Học sinh lớp 9 thường được đăng ký nguyện vọng trực tuyến tại trường THCS đang học", "steps": "1. Đăng ký nguyện vọng trực tuyến. 2. Nhận thẻ dự thi. 3. Tham gia kỳ thi. 4. Xem điểm chuẩn và nộp hồ sơ nhập học.", "category": "GIAODUC"},
    "BO_TUC_THCS": {"code": "BT_THCS", "name": "Tiếp nhận đối tượng học bổ túc trung học cơ sở", "docs": "Đơn xin học, Bản sao Giấy khai sinh, Học bạ (nếu đã học dở dang), CCCD", "coquan": "Trung tâm Giáo dục thường xuyên / Cơ sở GDTX cấp huyện", "time": "Theo kỳ tuyển sinh (thường vào tháng 8-9)", "lephi": "Miễn phí (hoặc theo quy định hỗ trợ của địa phương)", "note": "Dành cho người đã quá tuổi học THCS chính quy (từ 15 tuổi trở lên)", "steps": "1. Mua/Tải hồ sơ tại Trung tâm GDTX. 2. Điền thông tin và nộp lại kèm học bạ cũ. 3. Giám đốc Trung tâm ra quyết định tiếp nhận.", "category": "GIAODUC"},
    "DK_THI_THPT": {"code": "THI_THPT", "name": "Đăng ký dự thi tốt nghiệp trung học phổ thông", "docs": "Phiếu đăng ký dự thi, CCCD, Học bạ THPT, Giấy chứng nhận ưu tiên (nếu có)", "coquan": "Trường THPT (với thí sinh lớp 12) hoặc Sở GD&ĐT (với thí sinh tự do)", "time": "Theo lịch của Bộ GD&ĐT (thường vào tháng 4-5)", "lephi": "Lệ phí xét tuyển (tùy số lượng nguyện vọng ĐH)", "note": "Thí sinh lớp 12 thực hiện đăng ký 100% bằng hình thức trực tuyến (online)", "steps": "1. Nhận tài khoản từ trường. 2. Đăng nhập hệ thống của Bộ GD&ĐT. 3. Điền thông tin, chọn môn thi, nguyện vọng. 4. In phiếu, ký xác nhận.", "category": "GIAODUC"},

    # KHÁC (2)
    "XIN_VIEC": {"code":"XIN_VIEC","name":"Đăng ký tìm việc làm","docs":"CCCD, Hồ sơ xin việc","coquan":"Trung tâm dịch vụ việc làm","time":"1-2 ngày","lephi":"Miễn phí","note":"","steps":"1. Chuẩn bị hồ sơ xin việc (CCCD, sơ yếu lý lịch). 2. Đến Trung tâm dịch vụ việc làm. 3. Đăng ký và được giới thiệu việc làm.","category":"KHAC"},
    "TRO_CAP": {"code":"TRO_CAP","name":"Đăng ký trợ cấp xã hội","docs":"Đơn xin trợ cấp, CCCD, Sổ hộ khẩu","coquan":"UBND xã/phường","time":"7-10 ngày","lephi":"Miễn phí","note":"","steps":"1. Làm đơn xin trợ cấp xã hội. 2. Xin xác nhận hoàn cảnh khó khăn. 3. Nộp tại UBND xã/phường.","category":"KHAC"},
}


print(f'✅ Dataset Dịch vụ công: {len(DICHVUCONG_DATA)} thủ tục')
print()

# Lưu data
with open(os.path.join(DATA_DIR, 'data/dichvucong.json'), 'w', encoding='utf-8') as f:
    json.dump(DICHVUCONG_DATA, f, ensure_ascii=False, indent=2)
print('✅ Đã lưu data/dichvucong.json')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 4: BUILD DOCUMENTS & RAG SYSTEM
# ============================================================

print('='*70)
print('📦 XÂY DỰNG VECTOR DATABASE & RAG SYSTEM')
print('='*70)
print()

from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document
from rank_bm25 import BM25Okapi

# Build documents
print('🔄 Building documents...')
all_docs = []

# From Dataset
for i, row in df1.iterrows():
    # Truncate text to avoid Pinecone metadata size limit
    truncated_text = row['text'][:10000]  # Limit to 10,000 characters
    text = f"THỦ TỤC: {row['title']}\nNỘI DUNG: {truncated_text}"
    all_docs.append(Document(page_content=text, metadata={"source": "ds1", "id": i}))
print(f'   - Dataset: {len([d for d in all_docs if d.metadata["source"] == "ds1"])} docs')

# From Dịch vụ công
for code, proc in DICHVUCONG_DATA.items():
    text = f"""MÃ: {proc['code']}
THỦ TỤC: {proc['name']}
HỒ SƠ: {proc['docs']}
CƠ QUAN: {proc['coquan']}
THỜI GIAN: {proc['time']}
LỆ PHÍ: {proc['lephi']}
LƯU Ý: {proc['note']}
QUY TRÌNH: {proc['steps']}"""
    all_docs.append(Document(page_content=text, metadata={"source": "dvc", "code": code}))
print(f'   - Dịch vụ công: {len([d for d in all_docs if d.metadata["source"] == "dvc"])} docs')

print(f'\n✅ Total documents: {len(all_docs):,}')
print()

# Initialize Embeddings
print('🔄 Loading embeddings model...')
device = 'cuda' if torch.cuda.is_available() else 'cpu'
embeddings = HuggingFaceEmbeddings(
    model_name='keepitreal/vietnamese-sbert',
    model_kwargs={'device': device},
    encode_kwargs={'normalize_embeddings': True}
)
print('✅ Embeddings loaded!')
print()

# Configure Pinecone
print('🔄 Configuring Pinecone...')

# Import Pinecone libraries
from pinecone import Pinecone, PineconeApiException
from langchain_pinecone import PineconeVectorStore
import time

# Nhập PINECONE_API_KEY của bạn
# Lấy token tại: https://app.pinecone.io/keys
# Trên Hugging Face Spaces, set PINECONE_API_KEY trong Secrets
PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY', '')
index_name = 'ai-hanh-chinh-rag'
dimension = 768  # keepitreal/vietnamese-sbert dimension

# Global variables
vector_db = None
pc = None
pinecone_available = False

# Hàm kết nối Pinecone với retry
def connect_pinecone(max_retries=3):
    global pc, vector_db, pinecone_available

    if not PINECONE_API_KEY:
        print('⚠️ Cần thiết lập PINECONE_API_KEY trong Secrets của Hugging Face Space!')
        print('   1. Vào: https://app.pinecone.io/keys')
        print('   2. Tạo API Key')
        print('   3. Vào Settings → Repository secrets → Thêm: PINECONE_API_KEY')
        return False

    for attempt in range(max_retries):
        try:
            print(f'   🔑 Đang kết nối Pinecone (lần {attempt + 1}/{max_retries})...')

            # Initialize Pinecone client
            pc = Pinecone(api_key=PINECONE_API_KEY)

            # Kiểm tra index đã tồn tại chưa
            existing_indexes = [index['name'] for index in pc.list_indexes().get('indexes', [])]

            if index_name not in existing_indexes:
                print(f'   📝 Đang tạo index mới: {index_name}...')
                pc.create_index(
                    name=index_name,
                    dimension=dimension,
                    metric='cosine',
                    spec={'serverless': {'cloud': 'aws', 'region': 'us-east-1'}}
                )
                # Chờ index sẵn sàng
                print('   ⏳ Chờ index sẵn sàng...')
                time.sleep(10)
            else:
                print(f'   ✅ Index {index_name} đã tồn tại')

            # Kết nối vector store
            vector_db = PineconeVectorStore(
                index_name=index_name,
                embedding=embeddings,
                pinecone_api_key=PINECONE_API_KEY
            )

            # Kiểm tra số vectors trong index
            index = pc.Index(index_name)
            stats = index.describe_index_stats()
            vector_count = stats.get('total_vector_count', 0)
            print(f'   📊 Index hiện có: {vector_count:,} vectors')

            # Nếu index rỗng, thêm documents
            if vector_count == 0:
                print(f'   📤 Đang thêm {len(all_docs):,} documents vào Pinecone...')
                ids = [f"doc_{doc.metadata.get('source', 'unk')}_{i}" for i, doc in enumerate(all_docs)]

                # Thêm theo batch để tránh lỗi
                batch_size = 100
                for i in range(0, len(all_docs), batch_size):
                    batch_docs = all_docs[i:i+batch_size]
                    batch_ids = ids[i:i+batch_size]
                    vector_db.add_documents(documents=batch_docs, ids=batch_ids)
                    print(f'   ✅ Đã thêm {min(i+batch_size, len(all_docs))}/{len(all_docs)} documents')

                print('   ✅ Hoàn tất thêm documents vào Pinecone!')

            pinecone_available = True
            print('✅ Pinecone ready!')
            return True

        except PineconeApiException as e:
            print(f'   ⚠️ Pinecone API Error: {e}')
            if attempt < max_retries - 1:
                print(f'   🔄 Thử lại sau 2 giây...')
                time.sleep(2)
            else:
                print('   ❌ Không thể kết nối Pinecone sau {max_retries} lần thử')
                return False
        except Exception as e:
            print(f'   ❌ Lỗi kết nối Pinecone: {e}')
            if attempt < max_retries - 1:
                print(f'   🔄 Thử lại sau 2 giây...')
                time.sleep(2)
            else:
                print('   ❌ Không thể kết nối Pinecone sau {max_retries} lần thử')
                return False

    pinecone_available = False
    return False

# Kết nối Pinecone
connect_pinecone()
print()

# Create BM25
print('🔄 Creating BM25 index...')
corpus = [doc.page_content for doc in all_docs]
tokenized_corpus = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized_corpus)
print('✅ BM25 ready!')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 5: TRAIN CLASSIFIER PHÂN LOẠI TÌNH HUỐNG
# ============================================================

print('='*70)
print('🎯 TRAIN CLASSIFIER PHÂN LOẠI TÌNH HUỐNG')
print('='*70)
print()

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import RandomForestClassifier

# Danh sách tình huống từ DICHVUCONG_DATA
SITUATIONS = list(DICHVUCONG_DATA.keys())

# Tạo training data từ DICHVUCONG_DATA
X_train = []
y_train = []

for code, proc in DICHVUCONG_DATA.items():
    # Tạo các câu hỏi mẫu cho mỗi thủ tục
    questions = [
        f"Làm {proc['name'].lower()} cần gì?",
        f"{proc['name']} ở đâu?",
        f"Thủ tục {proc['name']} như thế nào?",
        proc['name'].lower(),
        f"Cách làm {proc['name'].lower()}",
    ]
    for q in questions:
        X_train.append(q)
        y_train.append(code)

# Thêm các câu hỏi chung
general_questions = [
    "cho tôi hỏi", "tôi muốn hỏi", "ai giúp tôi với",
    "cần làm gì", "ở đâu", "bao lâu", "bao nhiêu tiền"
]
for q in general_questions:
    X_train.append(q)
    y_train.append('hoi')

print(f'📊 Training data: {len(X_train)} samples')
print(f'   - Situations: {len(SITUATIONS)}')

# Train TF-IDF vectorizer
vec = TfidfVectorizer(max_features=1000)
X_train_vec = vec.fit_transform(X_train)

# Train classifier
clf = RandomForestClassifier(n_estimators=50, max_depth=10, random_state=42)
clf.fit(X_train_vec, y_train)

print('✅ Classifier trained successfully!')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 5.1: HYBRID SEARCH FUNCTION
# ============================================================
def hybrid_search(query, k_final=5):
    """Hybrid Search: Pinecone + BM25 với error handling"""

    # Danh sách kết quả
    dense_results = []
    sparse_results = []

    # 1. DENSE SEARCH từ Pinecone
    if pinecone_available and vector_db is not None:
        try:
            # Tìm kiếm với score (cosine similarity)
            results = vector_db.similarity_search_with_score(query, k=10)

            # Pinecone trả về (doc, score) với score là distance (càng nhỏ càng tốt)
            # Chuyển thành similarity (càng lớn càng tốt)
            for doc, distance in results:
                if distance < 0:
                    # Nếu score là similarity (đã chuẩn)
                    similarity = distance
                else:
                    # Nếu score là distance, chuyển thành similarity
                    similarity = 1 - distance

                dense_results.append({
                    'doc': doc,
                    'score': similarity,
                    'source': 'pinecone'
                })

        except Exception as e:
            print(f'⚠️ Pinecone search error: {e}, dùng fallback local docs...')
            # Fallback: dùng local documents nếu Pinecone lỗi
            dense_results = []

    # 2. SPARSE SEARCH từ BM25 (local)
    try:
        tokenized_query = query.split()
        bm25_scores = bm25.get_scores(tokenized_query)

        # Lấy top 10 kết quả
        top_indices = np.argsort(bm25_scores)[::-1][:10]

        for idx in top_indices:
            if idx < len(all_docs):
                sparse_results.append({
                    'doc': all_docs[idx],
                    'score': float(bm25_scores[idx]),
                    'source': 'bm25'
                })
    except Exception as e:
        print(f'⚠️ BM25 search error: {e}')
        sparse_results = []

    # 3. KẾT HỢP (Merge) kết quả
    combined = {}

    # Thêm kết quả từ Pinecone (ưu tiên cao hơn)
    for item in dense_results:
        doc_id = item['doc'].page_content[:100]
        combined[doc_id] = item

    # Thêm kết quả từ BM25 (nếu chưa có)
    for item in sparse_results:
        doc_id = item['doc'].page_content[:100]
        if doc_id not in combined:
            combined[doc_id] = item
        else:
            # Nếu đã có từ Pinecone, cộng thêm điểm BM25
            combined[doc_id]['score'] += item['score'] * 0.3

    # 4. SẮP XẾP và trả về top k_final
    sorted_results = sorted(combined.values(), key=lambda x: x['score'], reverse=True)[:k_final]

    # Trả về list documents
    return [item['doc'] for item in sorted_results]

# Hàm lấy documents trực tiếp từ Pinecone
def search_pinecone_only(query, k=5):
    """Tìm kiếm chỉ dùng Pinecone (RAG thuần túy)"""
    if not pinecone_available or vector_db is None:
        print('⚠️ Pinecone không khả dụng, dùng local documents...')
        return all_docs[:k]

    try:
        results = vector_db.similarity_search(query, k=k)
        return results
    except Exception as e:
        print(f'⚠️ Lỗi Pinecone: {e}')
        return all_docs[:k]

# Hàm lấy context từ RAG cho LLM
def get_rag_context(query, max_docs=5):
    """Lấy context từ RAG system để đưa vào prompt LLM"""
    docs = hybrid_search(query, k_final=max_docs)

    context_parts = []
    for i, doc in enumerate(docs, 1):
        text = doc.page_content.strip()
        if len(text) > 800:
            text = text[:800] + "..."
        context_parts.append(f"[Tài liệu {i}]: {text}")

    return "\n\n".join(context_parts)

print('✅ Hybrid Search function ready!')
print(f'   - Pinecone: {"✅ Connected" if pinecone_available else "❌ Not available"}')
print(f'   - BM25: ✅ Ready')
print(f'   - Total documents: {len(all_docs):,}')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 6: LOAD LLM (QWEN 2.5 3B)
# ============================================================

print('='*70)
print('🤖 LOAD LLM (QWEN 2.5 3B INSTRUCT)')
print('='*70)
print()

from transformers import AutoModelForCausalLM, AutoTokenizer

MODEL_ID = 'Qwen/Qwen2.5-3B-Instruct'

print(f'Model: {MODEL_ID}')

# Detect device
HAS_GPU = torch.cuda.is_available()
device_str = "GPU" if HAS_GPU else "CPU"
print(f'🖥️  Device: {device_str}')

if HAS_GPU:
    print('🔄 Loading model với 4-bit quantization (GPU mode)...')
    from transformers import BitsAndBytesConfig

    bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_use_double_quant=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=torch.bfloat16
    )

    tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)

    model = AutoModelForCausalLM.from_pretrained(
        MODEL_ID,
        quantization_config=bnb_config,
        device_map="auto",
        low_cpu_mem_usage=True,
        trust_remote_code=True
    )

    quant_type = "4-bit NF4"
else:
    print('🔄 Loading model với float16 (CPU mode - không dùng quantization)...')

    tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)

    model = AutoModelForCausalLM.from_pretrained(
        MODEL_ID,
        torch_dtype=torch.float16,
        device_map="auto",
        low_cpu_mem_usage=True,
        trust_remote_code=True
    )

    quant_type = "Float16 (CPU)"

print('✅ LLM loaded successfully!')
print(f'   - Device: {device_str}')
print(f'   - Quantization: {quant_type}')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 7: SETUP VOICE AI (WHISPER + EDGE-TTS) - LAZY LOAD
# ============================================================

print('='*70)
print('🎤 SETUP VOICE AI (LAZY MODE)')
print('='*70)
print()

import whisper
import edge_tts
import asyncio

# Flag để lazy load Whisper
whisper_model = None
whisper_device = 'cuda' if torch.cuda.is_available() else 'cpu'

def get_whisper_model():
    """Lazy load Whisper chỉ khi cần"""
    global whisper_model
    if whisper_model is None:
        print('🔄 Loading Whisper model (lazy load)...')
        whisper_model = whisper.load_model('tiny', device=whisper_device)  # Dùng 'tiny' thay 'base' để nhanh hơn
        print('✅ Whisper ready!')
    return whisper_model

def speech_to_text(audio_path):
    """Convert speech to text"""
    try:
        if audio_path and os.path.exists(audio_path):
            model = get_whisper_model()
            result = model.transcribe(audio_path, language='vi', fp16=False)
            return result["text"].strip()
    except Exception as e:
        print(f'Whisper error: {e}')
    return ""

async def text_to_speech(text, voice='vi-VN-HoaiMyNeural'):
    """Convert text to speech"""
    try:
        text = str(text)[:1500].replace('*','').replace('#','').replace('_','')
        timestamp = datetime.now().strftime('%H%M%S')
        output_path = os.path.join(DATA_DIR, f"audio_output/audio_{timestamp}.mp3")

        communicate = edge_tts.Communicate(text, voice)
        await communicate.save(output_path)
        return output_path
    except Exception as e:
        print(f'TTS error: {e}')
        return None

print('✅ Voice AI ready (Whisper sẽ load khi cần)!')
print()
print('='*70)




# ======================================================================
# ============================================================
# PHẦN 12.1: CÀI ĐẶT THƯ VIỆN NÂNG CAO
# ============================================================

print('='*70)
print('🚀 NÂNG CẤP AI PRO - IMPORT THƯ VIỆN')
print('='*70)
print()

# Thư viện bổ sung sẽ được cài đặt từ requirements.txt
# - sentencepiece>=0.1.99
# - protobuf>=3.20.0
print('✅ Sẵn sàng!')
print()

# Import bổ sung
from functools import lru_cache
from typing import List, Dict, Tuple
import re
import hashlib
import time

print('='*70)

# ======================================================================
# ============================================================
# PHẦN 12.2: PROMPT ENGINE NÂNG CAO (PERSONA-DRIVEN)
# ============================================================

print('='*70)
print('🎯 PROMPT ENGINE NÂNG CAO')
print('='*70)
print()

# Định nghĩa các persona cho AI
AI_PERSONAS = {
    "thuong": {
        "name": "Chị Thuong - Cán bộ tư vấn thân thiện",
        "tone": "thân thiện, gần gũi, dùng từ ngữ bình dân",
        "greeting": ["Chào bác/cháu ạ,", "Dạ vâng,", "Bác/cháu hỏi đúng người rồi ạ,"],
        "closing": ["Có gì bác/chứ cứ hỏi thêm nhé!", "Chúc bác/cháu làm thủ tục thuận lợi ạ!"],
        "style": "conversational"
    },
    "chuyen": {
        "name": "Anh Chuyen - Chuyên viên hành chính công",
        "tone": "chuyên nghiệp, ngắn gọn, súc tích",
        "greeting": ["Dạ, về vấn đề", "Theo quy định,", "Về thủ tục"],
        "closing": ["Trân trọng!", "Thông tin trên để tham khảo ạ."],
        "style": "professional"
    },
    "chi_tiet": {
        "name": "Cô Chi Tiet - Cán bộ hướng dẫn chi tiết",
        "tone": "chi tiết, từng bước, cẩn thận",
        "greeting": ["Dạ để em hướng dẫn chi tiết cho bác/cháu ạ,", "Em sẽ giải thích kỹ càng ạ,"],
        "closing": ["Bác/cháu làm theo như trên là được ạ!", "Nếu chưa hiểu chỗ nào cứ hỏi thêm ạ!"],
        "style": "detailed"
    }
}

# Query Patterns để hiểu ý định người dùng
QUERY_PATTERNS = {
    "can_gi": ["cần gì", "giấy tờ gì", "chuẩn bị gì", "hồ sơ gì", "documents"],
    "o_day": ["ở đâu", "nơi nào", "địa chỉ", "đ到哪里", "đến đâu"],
    "bao_lau": ["bao lâu", "mất bao lâu", "thời gian", "how long", "bao nhiêu ngày"],
    "bao_nhieu": ["bao nhiêu", "bao tiền", "phí", "lệ phí", "cost", "giá"],
    "the_quy_trinh": ["quy trình", "cách làm", "làm thế nào", "làm sao", "hướng dẫn", "how to"],
    "nguoi_nuoc_ngoai": ["người nước ngoài", "ngoài", "không phải VN", "foreigner"],
    "qua_han": ["qua hạn", "quá hạn", "muộn", "sau thời hạn", "overdue"],
    "mat": ["mất", "thất lạc", "đánh rơi", "không thấy", "lost"],
    "doi": ["đổi", "làm lại", "cập nhật", "thay đổi", "renew", "change"],
    "moi": ["lần đầu", "mới", "chưa từng làm", "first time", "new"],
}

# Query Expansion - Mở rộng câu hỏi
def expand_query(query: str) -> List[str]:
    """Mở rộng câu hỏi để tìm kiếm tốt hơn"""
    expanded_queries = [query]
    query_lower = query.lower()

    # Thêm từ đồng nghĩa
    for pattern, keywords in QUERY_PATTERNS.items():
        for kw in keywords:
            if kw in query_lower:
                # Thêm các từ khóa liên quan
                for related_kw in keywords:
                    if related_kw not in query_lower:
                        expanded_queries.append(query_lower.replace(kw, related_kw))

    return list(set(expanded_queries))

# Detect intent from query
def detect_intent(query: str) -> Dict[str, any]:
    """Phát hiện ý định người dùng từ câu hỏi"""
    query_lower = query.lower()
    intents = {
        "need_docs": False,
        "need_location": False,
        "need_time": False,
        "need_fee": False,
        "need_process": False,
        "is_foreign": False,
        "is_overdue": False,
        "is_lost": False,
        "is_renewal": False,
        "is_new": False
    }

    for pattern, keywords in QUERY_PATTERNS.items():
        for kw in keywords:
            if kw in query_lower:
                intents[pattern] = True

    return intents

# Dynamic Response Templates
RESPONSE_TEMPLATES = {
    " khai_sinh": {
        "short": "Dạ để khai sinh cho bé, bác/cháu cần mang: Giấy khai sinh, CCCD của cả cha mẹ, và sổ hộ khẩu. Ra UBND xã/phường nơi cư trú là được ạ!",
        "detailed": """Dạ về khai sinh cho bé, em hướng dẫn chi tiết ạ:

📋 **HỒ SƠ CẦN CHUẨN BỊ:**
1. Giấy khai sinh (điền đầy đủ thông tin)
2. CCCD/CMND của cả cha và mẹ
3. Sổ hộ khẩu của cha mẹ

🏢 **NƠI LÀM:** UBND xã/phường nơi cư trú

⏰ **THỜI GIAN:** 1-3 ngày

💰 **LỆ PHÍ:** Miễn phí

⚠️ **LƯU Ý:** Làm trong 60 ngày kể từ khi sinh để tránh phạt nhé!"""
    }
}

print('✅ Prompt Engine đã sẵn sàng!')
print(f'   - Personas: {len(AI_PERSONAS)}')
print(f'   - Query Patterns: {len(QUERY_PATTERNS)}')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 12.3: CONTEXT MEMORY & CHAIN OF THOUGHT
# ============================================================

print('='*70)
print('🧠 CONTEXT MEMORY & CHAIN OF THOUGHT')
print('='*70)
print()

# Conversation Memory with Context Tracking
class ConversationMemory:
    def __init__(self, max_history=10):
        self.history = []  # Lưu lịch sử chat
        self.context = {}  # Lưu ngữ cảnh (tình huống, giấy tờ đã có, etc.)
        self.max_history = max_history
        self.persona = "thuong"  # Persona mặc định

    def add_message(self, role: str, content: str, metadata: dict = None):
        """Thêm tin nhắn vào lịch sử"""
        self.history.append({
            "role": role,
            "content": content,
            "metadata": metadata or {},
            "timestamp": time.time()
        })

        # Giữ lịch sử trong giới hạn
        if len(self.history) > self.max_history * 2:
            self.history = self.history[-self.max_history * 2:]

    def update_context(self, key: str, value: any):
        """Cập nhật ngữ cảnh"""
        self.context[key] = value

    def get_context_summary(self) -> str:
        """Tóm tắt ngữ cảnh hiện tại"""
        if not self.context:
            return ""

        summary_parts = []
        if "situation" in self.context:
            summary_parts.append(f"Vấn đề: {self.context['situation']}")
        if "has_docs" in self.context:
            summary_parts.append(f"Đã có giấy tờ: {self.context['has_docs']}")
        if "location" in self.context:
            summary_parts.append(f"Khu vực: {self.context['location']}")

        return " | ".join(summary_parts) if summary_parts else ""

    def get_relevant_history(self, current_query: str) -> str:
        """Lấy lịch sử liên quan đến câu hỏi hiện tại"""
        if not self.history:
            return ""

        # Lấy 3-4 tin nhắn gần nhất
        recent = self.history[-6:]
        relevant = []

        for msg in recent:
            if msg["role"] == "user":
                relevant.append(f"Người dùng: {msg['content']}")
            elif msg["role"] == "assistant":
                # Chỉ lấy tóm tắt
                content = msg['content'][:200]
                relevant.append(f"AI: {content}...")

        return "\n".join(relevant)

    def set_persona(self, persona: str):
        """Thay đổi persona"""
        if persona in AI_PERSONAS:
            self.persona = persona

    def get_persona(self) -> dict:
        """Lấy persona hiện tại"""
        return AI_PERSONAS.get(self.persona, AI_PERSONAS["thuong"])

    def detect_situation_from_history(self) -> str:
        """Phát hiện tình huống từ lịch sử"""
        for msg in reversed(self.history):
            if msg.get("metadata", {}).get("situation"):
                return msg["metadata"]["situation"]
        return ""

    def clear(self):
        """Xóa lịch sử và ngữ cảnh"""
        self.history = []
        self.context = {}

# Global memory instance
conversation_memory = ConversationMemory()

# Chain of Thought Prompting
def build_chain_of_thought_prompt(query: str, situation: str, context: str,
                                   retrieved_docs: list, intents: dict) -> str:
    """Xây dựng prompt với Chain of Thought để suy luận từng bước"""

    persona = conversation_memory.get_persona()

    # Bước 1: Xác định vấn đề
    step1 = f"""BƯỚC 1 - XÁC ĐỊNH VẤN ĐỀ:
Người dùng đang hỏi về: {situation.replace('_', ' ').upper()}
Câu hỏi cụ thể: "{query}"
"""

    # Bước 2: Phân tích ý định
    intent_list = [k for k, v in intents.items() if v]
    step2 = f"""BƯỚC 2 - PHÂN TÍCH Ý ĐỊNH:
Người dùng muốn biết: {', '.join(intent_list) if intent_list else 'thông tin chung'}
"""

    # Bước 3: Lấy thông tin từ tài liệu
    step3 = """BƯỚC 3 - TRA CỨU THÔNG TIN:
Dựa trên tài liệu và quy định hiện hành:
"""
    for i, doc in enumerate(retrieved_docs[:3], 1):
        doc_text = doc.page_content.strip()[:300]
        step3 += f"\n[i] {doc_text}..."

    # Bước 4: Tổng hợp câu trả lời
    step4 = f"""BƯỚC 4 - TỔNG HỢP TRẢ LỜI:
Hãy trả lời theo phong cách: {persona['tone']}

Lưu ý:
- Dùng các mẫu câu: {', '.join(persona['greeting'][:2])}
- Kết thúc bằng: {', '.join(persona['closing'][:2])}
- Tránh lặp lại từ ngữ, thay đổi cách diễn đạt
- Nếu người dùng hỏi ngắn gọn, trả lời ngắn gọn
- Nếu người dùng hỏi chi tiết, trả lời đầy đủ
"""

    if context:
        step4 += f"\n- Ngữ cảnh trước đó: {context}"

    return step4

# Dynamic Temperature based on query complexity
def calculate_temperature(query: str, situation: str) -> float:
    """Tính độ sáng tạo phù hợp dựa trên độ phức tạp câu hỏi"""

    # Câu hỏi đơn giản, cần chính xác cao
    simple_patterns = ["cần gì", "ở đâu", "bao lâu", "bao nhiêu"]
    for pattern in simple_patterns:
        if pattern in query.lower():
            return 0.1  # Thấp, tập trung vào chính xác

    # Câu hỏi phức tạp, cần linh hoạt
    complex_patterns = ["nhưng", "tuy nhiên", "trong trường hợp", "vậy nếu", "làm sao khi"]
    for pattern in complex_patterns:
        if pattern in query.lower():
            return 0.4  # Cao hơn, linh hoạt hơn

    # Câu hỏi tình huống đặc biệt
    if "qua_han" in situation or "mat" in situation or "doi" in situation:
        return 0.3  # Trung bình

    return 0.2  # Mặc định

print('✅ Context Memory & CoT đã sẵn sàng!')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 12.4: AI PROCESS PRO - PHIÊN BẢN NÂNG CẤP
# ============================================================



# Response Diversity - Tránh lặp lại
import random

def get_diverse_greeting(persona_key: str) -> str:
    """Lấy lời chào ngẫu nhiên để đa dạng hóa"""
    persona = AI_PERSONAS.get(persona_key, AI_PERSONAS["thuong"])
    return random.choice(persona["greeting"])

def get_diverse_closing(persona_key: str) -> str:
    """Lấy lời kết ngẫu nhiên"""
    persona = AI_PERSONAS.get(persona_key, AI_PERSONAS["thuong"])
    return random.choice(persona["closing"])

# Advanced RAG với Re-ranking
def advanced_hybrid_search(query: str, k_final: int = 3) -> list:
    """Tìm kiếm nâng cao với re-ranking - Tối ưu tốc độ"""

    # Query expansion - giới hạn để tăng tốc
    expanded_queries = expand_query(query)

    # Tìm kiếm với các query mở rộng - giảm từ 3 xuống 2
    all_results = {}
    for expanded_q in expanded_queries[:2]:  # Giảm từ 3 xuống 2 để nhanh hơn
        docs = hybrid_search(expanded_q, k_final=5)  # Giảm từ 8 xuống 5
        for doc in docs:
            doc_id = doc.page_content[:50]
            if doc_id not in all_results:
                all_results[doc_id] = {"doc": doc, "score": 1.0}
            else:
                all_results[doc_id]["score"] += 0.5

    # Re-ranking dựa trên relevance
    query_words = set(query.lower().split())
    for doc_id, item in all_results.items():
        doc_words = set(item["doc"].page_content.lower().split())
        overlap = len(query_words & doc_words)
        item["score"] += overlap * 0.1

    # Sort và trả về
    sorted_results = sorted(all_results.values(), key=lambda x: x["score"], reverse=True)
    return [item["doc"] for item in sorted_results[:k_final]]

# Smart Response Builder
def build_smart_response(query: str, situation: str, docs: list, intents: dict,
                        proc_info: str = "") -> str:
    """Xây dựng câu trả lời thông minh, tự nhiên"""

    persona = conversation_memory.get_persona()
    persona_key = conversation_memory.persona

    # Xác định độ dài câu trả lời dựa trên câu hỏi
    query_length = len(query.split())
    if query_length <= 5 and any([intents["need_docs"], intents["need_location"]]):
        # Câu hỏi ngắn, trả lời ngắn gọn
        response_style = "short"
    else:
        response_style = "detailed"

    # Lấy thông tin thủ tục
    proc_data = None
    for code, proc in DICHVUCONG_DATA.items():
        if situation.replace("_", "").upper() in code or code in situation.upper():
            proc_data = proc
            break

    # Xây dựng câu trả lời
    parts = []

    # Lời chào ngẫu nhiên
    greeting = get_diverse_greeting(persona_key)
    parts.append(greeting)

    # Phần thân câu trả lời
    if response_style == "short" and proc_data:
        # Trả lời ngắn gọn
        if intents["need_docs"]:
            parts.append(f"\nĐể {proc_data['name'].lower()}, bác/cháu cần chuẩn bị:")
            docs_list = proc_data['docs'].split(',')
            for doc in docs_list:
                parts.append(f"- {doc.strip()}")

        if intents["need_location"]:
            parts.append(f"\nRa làm ở: {proc_data['coquan']}")

        if intents["need_time"]:
            parts.append(f"\nThời gian: {proc_data['time']}")

        if intents["need_fee"]:
            parts.append(f"\nLệ phí: {proc_data['lephi']}")

        if intents["need_process"]:
            parts.append(f"\nCách làm: {proc_data['steps'][:100]}...")

    else:
        # Trả lời chi tiết
        if proc_data:
            parts.append(f"\nem sẽ hướng dẫn về **{proc_data['name']}** ạ:")

            if intents["need_docs"] or not any(intents.values()):
                parts.append(f"\n📋 **Giấy tờ cần chuẩn bị:**")
                docs_list = proc_data['docs'].split(',')
                for i, doc in enumerate(docs_list, 1):
                    parts.append(f"{i}. {doc.strip()}")

            if intents["need_location"] or not any(intents.values()):
                parts.append(f"\n🏢 **Nơi làm thủ tục:** {proc_data['coquan']}")

            if intents["need_time"] or not any(intents.values()):
                parts.append(f"\n⏰ **Thời gian:** {proc_data['time']}")

            if intents["need_fee"] or not any(intents.values()):
                parts.append(f"\n💰 **Lệ phí:** {proc_data['lephi']}")

            if proc_data.get('note'):
                parts.append(f"\n⚠️ **Lưu ý:** {proc_data['note']}")

            if intents["need_process"] or not any(intents.values()):
                parts.append(f"\n📝 **Quy trình:**")
                steps = proc_data['steps'].split('.')
                for step in steps:
                    step = step.strip()
                    if step and len(step) > 3:
                        parts.append(f"- {step}")
        else:
            # Không có dữ liệu thủ tục cụ thể
            parts.append(f"\nvề vấn đề bác/cháu hỏi, em xin được tư vấn ạ:")

            # Dùng thông tin từ retrieved docs
            for i, doc in enumerate(docs[:2], 1):
                doc_text = doc.page_content.strip()[:200]
                parts.append(f"\nDựa trên tài liệu [{i}]: {doc_text}...")

    # Lời kết ngẫu nhiên
    closing = get_diverse_closing(persona_key)
    parts.append(f"\n\n{closing}")

    return "\n".join(parts)

# AI Process PRO - Hàm chính
def ai_process_pro(query: str, use_memory: bool = True) -> dict:
    """
    AI Process PRO với tất cả các cải tiến:
    - Context Memory
    - Chain of Thought
    - Dynamic Temperature
    - Response Diversity
    - Advanced RAG
    """
    try:
        start_time = time.time()

        # Clean memory
        torch.cuda.empty_cache()
        gc.collect()

        # Bước 1: Phân tích câu hỏi
        intents = detect_intent(query)

        # Bước 2: Phân loại tình huống
        try:
            situation = clf.predict(vec.transform([query]))[0]
        except:
            # Fallback: detect từ query patterns
            for pattern, keywords in QUERY_PATTERNS.items():
                for kw in keywords:
                    if kw in query.lower():
                        situation = pattern
                        break
                else:
                    continue
                break
            else:
                situation = 'hoi'

        # Bước 3: Advanced RAG retrieval
        docs = advanced_hybrid_search(query, k_final=3)  # Giảm từ 5 xuống 3

        # Bước 4: Lấy thông tin thủ tục
        proc_info = ""
        proc_data = None
        for code, proc in DICHVUCONG_DATA.items():
            if situation.replace("_", "").upper() in code or code in situation.upper():
                proc_data = proc
                proc_info = f"""
THỦ TỤC: {proc['name']}
HỒ SƠ: {proc['docs']}
CƠ QUAN: {proc['coquan']}
THỜI GIAN: {proc['time']}
LỆ PHÍ: {proc['lephi']}
LƯU Ý: {proc['note']}
QUY TRÌNH: {proc['steps']}
"""
                break

        # Bước 5: Lấy ngữ cảnh từ memory
        context_summary = ""
        relevant_history = ""
        if use_memory:
            conversation_memory.update_context("situation", situation)
            context_summary = conversation_memory.get_context_summary()
            relevant_history = conversation_memory.get_relevant_history(query)

        # Bước 6: Tính toán động temperature
        temperature = calculate_temperature(query, situation)

        # Bước 7: Xây dựng prompt với Chain of Thought
        cot_prompt = build_chain_of_thought_prompt(
            query=query,
            situation=situation,
            context=context_summary,
            retrieved_docs=docs,
            intents=intents
        )

        # Bước 8: Full prompt cho LLM
        full_prompt = f"""Bạn là cán bộ tư vấn thủ tục hành chính công tại UBND. Hãy hỗ trợ người dân.

{cot_prompt}

CÂU HỎI: {query}

{proc_info}

LỊCH SỬ ĐỌC CHUẨN KHI CÓ:
{relevant_history}

HÃY TRẢ LỜI NGƯỜI DÂNG:"""

        messages = [
            {"role": "system", "content": "Bạn là cán bộ UBND tận tâm, luôn hỗ trợ người dân nhiệt tình. Trả lời bằng tiếng Việt, ngôn ngữ tự nhiên, gần gũi như cán bộ thực tế đang tư vấn."},
            {"role": "user", "content": full_prompt}
        ]

        text_prompt = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True
        )

        # Bước 9: Generate response với dynamic temperature - dùng model.generate trực tiếp
        inputs = tokenizer(text_prompt, return_tensors="pt").to(model.device)
        input_length = inputs.input_ids.shape[1] # Lấy độ dài của prompt đầu vào

        outputs = model.generate(
            **inputs,
            max_new_tokens=512, # Tăng lên một chút để tránh bị cắt chữ
            temperature=temperature,
            top_p=0.9,
            repetition_penalty=1.15,
            do_sample=temperature > 0.2
        )

# CHỈ giải mã những token mới (phần sau input_length)
        response_text = tokenizer.decode(outputs[0][input_length:], skip_special_tokens=True)

        # Clean response
        if "<|im_start|>assistant\n" in response_text:
            response_text = response_text.split("<|im_start|>assistant\n")[-1]
        if "<|im_end|>" in response_text:
            response_text = response_text.split("<|im_end|")[0]

        llm_response = response_text.strip()

        # Bước 10: Post-process để đảm bảo tự nhiên
        # Loại bỏ tiếng Trung, ký tự lạ
        clean_lines = []
        for line in llm_response.split('\n'):
            # Filter Chinese characters
            if any('\u4e00' <= char <= '\u9fff' for char in line):
                continue
            # Filter unwanted phrases
            unwanted_keywords = ["Infrastructure", "Last Updated", "Step 1 -", "BƯỚC 1", "BƯỚC 2", "BƯỚC 3", "BƯỚC 4"]
            if any(phrase in line for phrase in unwanted_keywords):
                continue
            clean_lines.append(line)

        final_response = '\n'.join(clean_lines).strip()

        # Fallback nếu response quá ngắn
        if len(final_response) < 50:
            # Dùng smart response builder thay vì prompt mặc định
            final_response = build_smart_response(query, situation, docs, intents, proc_info)
        if "persons" in final_response.lower():
            final_response = final_response.replace("Persons", "hành chính về trật tự xã hội")
            final_response = final_response.replace("persons", "hành chính về trật tự xã hội")

        # Fallback cuối cùng
        if len(final_response) < 30:
            final_response = "Dạ bác/cháu hỏi đúng người rồi ạ! Hiện tại em chưa có đầy đủ thông tin về trường hợp này. Bác/cháu vui lòng mang giấy tờ tùy thân ra trực tiếp UBND Phường/Xã để được hỗ trợ chi tiết hơn ạ!"

        # Lưu vào memory
        if use_memory:
            conversation_memory.add_message("user", query, {"situation": situation})
            conversation_memory.add_message("assistant", final_response, {"situation": situation})

        # Clean memory
        torch.cuda.empty_cache()
        gc.collect()

        process_time = time.time() - start_time

        return {
            "situation": situation,
            "response": final_response,
            "docs_count": len(docs),
            "process_time": round(process_time, 2),
            "intents": intents,
            "temperature": temperature
        }

    except Exception as e:
        print(f'AI Process PRO Error: {e}')
        import traceback
        traceback.print_exc()

        # Fallback với smart response
        try:
            fallback_response = build_smart_response(
                query,
                conversation_memory.detect_situation_from_history() or 'hoi',
                [],
                detect_intent(query),
                ""
            )
            return {
                "situation": "fallback",
                "response": fallback_response,
                "docs_count": 0,
                "process_time": 0.5
            }
        except:
            return {
                "situation": "error",
                "response": "Dạ xin lỗi, hệ thống đang bận. Bác/cháu vui lòng thử lại sau hoặc đến trực tiếp UBND để được hỗ trợ ạ!",
                "docs_count": 0,
                "process_time": 0
            }

print('✅ AI Process PRO đã sẵn sàng!')
print()
print('Cải tiến:')
print('  - Context Memory: Nhớ lịch sử chat')
print('  - Chain of Thought: Suy luận từng bước')
print('  - Dynamic Temperature: Tự điều chỉnh độ sáng tạo')
print('  - Response Diversity: Tránh lặp lại câu trả lời')
print('  - Advanced RAG: Tìm kiếm chính xác hơn')
print()
print('='*70)

# ======================================================================
# ============================================================
# PHẦN 12.5: GRADIO INTERFACE
# ============================================================

import gradio as gr
print('='*70)
print('🖥️ KHỞI TẠO GRADIO INTERFACE')
print('='*70)
print()

# Admin functions
admin_procedures = list(DICHVUCONG_DATA.values())

def get_procedures_list():
    lines = ["| Mã | Tên thủ tục | Cơ quan |"]
    lines.append("|---|---|---|")
    for p in admin_procedures[:20]: # Limit to 20 for display
        lines.append(f"| {p['code']} | {p['name']} | {p['coquan']} |")
    return "\n".join(lines)

def admin_add(code, name, docs, coquan, time_val, lephi, note, steps):
    global admin_procedures
    new_proc = {
        'code': code, 'name': name, 'docs': docs, 'coquan': coquan,
        'time': time_val, 'lephi': lephi, 'note': note, 'steps': steps
    }
    admin_procedures.append(new_proc)
    # For simplicity, we are not updating DICHVUCONG_DATA or Pinecone here directly
    # In a real application, you would update the backend data source
    return f"✅ Đã thêm: {code} - {name}", get_procedures_list()

# Chat function với AI Process PRO
def process_chat_pro(audio, text, history, persona="thuong"):
    try:
        if history is None:
            history = []

        # Cập nhật persona
        conversation_memory.set_persona(persona)

        # Xử lý input
        query = ''
        if audio and not os.path.isdir(audio):
            query = speech_to_text(audio)
        elif text and text.strip():
            query = text.strip()

        if len(query) < 3:
            return history, None, ''

        # Dùng AI Process PRO
        result = ai_process_pro(query, use_memory=True)

        # Format response
        response = f"""

{result['response']}

---
_⏱️ Xử lý: {result['process_time']}s | 📄 Tài liệu: {result['docs_count']} | 🌡️ Temp: {result['temperature']}_"""

        history.append({"role": "user", "content": query})
        history.append({"role": "assistant", "content": response})

        # TTS
        audio_out = None
        try:
            loop = asyncio.get_event_loop()
        except:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

        try:
            audio_out = loop.run_until_complete(text_to_speech(result['response']))
        except:
            pass

        return history, audio_out, ''

    except Exception as e:
        import traceback
        traceback.print_exc()
        error_msg = f"Lỗi: {str(e)}"
        return history, None, error_msg

def clear_chat_pro():
    conversation_memory.clear()
    return [], None, ''

def change_persona(new_persona):
    conversation_memory.set_persona(new_persona)
    persona_names = {
        "thuong": "Chị Thuong - Thân thiện",
        "chuyen": "Anh Chuyen - Chuyên nghiệp",
        "chi_tiet": "Cô Chi Tiet - Chi tiết"
    }
    return f"✅ Đã chuyển sang: {persona_names.get(new_persona, new_persona)}"

# Build PRO interface
custom_css = """
/* Reset font & nền */
.gradio-container {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
    background-color: #f8f9fa !important; 
}

/* Ẩn footer mặc định */
footer {display: none !important;}

/* Thanh Topbar chuyên nghiệp (Gradient Xanh Navy) */
.admin-topbar {
    background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
    padding: 20px 30px;
    display: flex;
    align-items: center;
    margin: -20px -20px 20px -20px; /* Tràn viền */
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    color: white;
}

.admin-icon {
    font-size: 45px;
    margin-right: 20px;
    background: rgba(255,255,255,0.1);
    padding: 10px;
    border-radius: 12px;
}

.admin-brand h1 {
    color: #ffffff !important;
    font-size: 24px !important;
    font-weight: 600 !important;
    margin: 0 !important;
    letter-spacing: 1px;
    text-transform: uppercase;
}

.admin-brand p {
    color: #e0e0e0 !important;
    font-size: 14px !important;
    margin: 5px 0 0 0 !important;
    font-weight: 300 !important;
}

/* Tinh chỉnh Nút bấm */
button.primary {
    background-color: #0056b3 !important;
    color: white !important;
    border-radius: 6px !important;
    font-weight: 500 !important;
    border: none !important;
    transition: all 0.3s ease;
}

button.primary:hover {
    background-color: #004494 !important;
    box-shadow: 0 4px 8px rgba(0,86,179,0.3) !important;
}

/* Tùy chỉnh Khung Chat */
.main-chatbot {
    border: 1px solid #dee2e6 !important;
    border-radius: 8px !important;
    background-color: #ffffff !important;
    box-shadow: 0 2px 15px rgba(0,0,0,0.03) !important;
}

/* Tiêu đề Box */
.section-title {
    color: #1e3c72;
    font-weight: 600;
    margin-bottom: 10px;
    border-bottom: 2px solid #e9ecef;
    padding-bottom: 5px;
}
"""

# Khởi tạo giao diện với Theme base màu xanh thanh lịch
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"), css=custom_css) as demo:
    
    # 1. TOPBAR CHUYÊN NGHIỆP (Không dùng logo/tên thật)
    gr.HTML("""
    <div class="admin-topbar">
        <div class="admin-icon">🏛️</div>
        <div class="admin-brand">
            <h1>HỆ THỐNG TRỢ LÝ ẢO TƯ VẤN HÀNH CHÍNH</h1>
            <p>Giải đáp thông tin thủ tục - Nhanh chóng, Chính xác, Hỗ trợ 24/7</p>
        </div>
    </div>
    """)

    with gr.Tabs():
        # Chat Tab
        with gr.Tab("💬 Trợ lý AI"):
            with gr.Row():
                with gr.Column(scale=7):
                    chat = gr.Chatbot(height=550, label='Hệ thống tư vấn tự động', elem_classes="main-chatbot")
                    audio_out = gr.Audio(label='🔊 Nghe phản hồi', autoplay=True)

                with gr.Column(scale=3):
                    gr.HTML("<div class='section-title'>Tra cứu thông tin</div>")
                    msg_in = gr.Textbox(label='Nhập câu hỏi của bạn', lines=3,
                                        placeholder='VD: Điều kiện để cấp lại căn cước công dân là gì?')
                    audio_in = gr.Audio(sources=['microphone'], type='filepath', label='🎤 Tìm kiếm bằng giọng nói')
                    
                    with gr.Row():
                        btn_send = gr.Button('GỬI CÂU HỎI', variant='primary')
                        btn_clear = gr.Button('Làm mới')

                    # --- Ô HƯỚNG DẪN NHỎ THÊM VÀO ĐÂY ---
                    gr.HTML("""
                        <div style="
                            margin-top: 15px; 
                            padding: 10px; 
                            border-radius: 6px; 
                            background-color: #f0f7ff; 
                            border: 1px dashed #2a5298; 
                            font-size: 13px; 
                            line-height: 1.5; 
                            color: #334155;
                        ">
                            <strong style="color: #1e3c72;">💡 Cách dùng nhanh:</strong>
                            <ul style="margin: 5px 0 0 15px; padding: 0;">
                                <li>Nhập câu hỏi hoặc dùng 🎤 <b>Micro</b>.</li>
                                <li>Nhấn <b>Gửi</b> để nhận phản hồi từ AI.</li>
                                <li>Hệ thống sẽ tự động 🔊 <b>Phát loa</b> câu trả lời.</li>
                            </ul>
                        </div>
                    """)

            gr.Markdown("---")
            gr.Markdown("**📌 Gợi ý tra cứu nhanh:**")
            gr.Examples(
                examples=[
                    [None, 'Làm bằng lái xe máy cần giấy tờ gì?'],
                    [None, 'Khai sinh quá hạn phải làm sao?'],
                    [None, 'Đăng ký xe ô tô mới ở đâu?'],
                    [None, 'Tách hộ khẩu cần những gì?'],
                    [None, 'Làm hộ chiếu bao nhiêu tiền?'],
                ],
                inputs=[audio_in, msg_in]
            )

            # (Các sự kiện click giữ nguyên)
            btn_send.click(process_chat_pro, [audio_in, msg_in, chat], [chat, audio_out, msg_in])
            msg_in.submit(process_chat_pro, [audio_in, msg_in, chat], [chat, audio_out, msg_in])
            btn_clear.click(clear_chat_pro, [], [chat, audio_out, msg_in])
        with gr.Tab("⚙️ Admin Panel"):
            gr.Markdown("### 🔐 Xác thực quyền quản trị")
            
            with gr.Row():
                input_pass = gr.Textbox(
                    label="Nhập mật khẩu để mở khóa tính năng Admin", 
                    type="password", 
                    placeholder="nhap mat khau",
                    scale=4
                )
                btn_login = gr.Button("Xác thực 🔑", scale=1)
            
            # Group chứa nội dung Admin - Mặc định ẩn (visible=False)
            admin_content = gr.Group(visible=False) 
            
            with admin_content:
                gr.Markdown("---")
                gr.Markdown("### ➕ Thêm thủ tục mới (AI sẽ học ngay)")
                with gr.Row():
                    with gr.Column():
                        admin_code = gr.Textbox(label="Mã thủ tục")
                        admin_name = gr.Textbox(label="Tên thủ tục")
                        admin_docs = gr.Textbox(label="Hồ sơ cần chuẩn bị", lines=2)
                        admin_coquan = gr.Textbox(label="Cơ quan tiếp nhận")

                    with gr.Column():
                        admin_time = gr.Textbox(label="Thời gian")
                        admin_lephi = gr.Textbox(label="Lệ phí")
                        admin_note = gr.Textbox(label="Lưu ý", lines=2)
                        admin_steps = gr.Textbox(label="Quy trình", lines=3)

                btn_add = gr.Button("➕ THÊM VÀO HỆ THỐNG", variant='primary', size='lg')
                add_output = gr.Textbox(label="Kết quả xử lý")
                procedures_list = gr.Markdown(value=get_procedures_list())

            # --- Logic xử lý ẩn hiện và mật khẩu ---
            def check_admin(password):
                if password == ADMIN_PASSWORD:
                    # Nếu đúng: Hiện nội dung admin, ẩn ô nhập mật khẩu và hiện thông báo thành công
                    gr.Info("Đăng nhập thành công! Quyền quản trị đã được mở.")
                    return gr.update(visible=True), gr.update(visible=False)
                else:
                    # Nếu sai: Giữ ẩn nội dung và hiện cảnh báo
                    raise gr.Error("Mật khẩu không chính xác. Vui lòng thử lại!")
                    return gr.update(visible=False), gr.update(visible=True)

            # Sự kiện khi nhấn nút Xác thực
            btn_login.click(
                check_admin, 
                inputs=[input_pass], 
                outputs=[admin_content, input_pass]
            )

            # Sự kiện khi nhấn nút THÊM (giữ nguyên logic cũ của bạn)
            btn_add.click(
                admin_add,
                [admin_code, admin_name, admin_docs, admin_coquan, admin_time, admin_lephi, admin_note, admin_steps],
                [add_output, procedures_list]
            )

        # Memory Tab (mới)
        with gr.Tab("🧠 Memory Manager"):
            gr.Markdown("### 📊 Quản lý bộ nhớ hội thoại")

            memory_info = gr.Textbox(label="Ngữ cảnh hiện tại", interactive=False, lines=3)
            memory_history = gr.Textbox(label="Lịch sử chat", interactive=False, lines=10)

            with gr.Row():
                btn_refresh = gr.Button("🔄 Làm mới")
                btn_clear_mem = gr.Button("🗑️ Xóa memory", variant='stop')

            def refresh_memory():
                ctx = conversation_memory.get_context_summary()
                hist = "\n".join([f"{m['role']}: {m['content'][:100]}..."
                                 for m in conversation_memory.history[-5:]])
                return ctx or "Không có ngữ cảnh", hist or "Không có lịch sử"

            btn_refresh.click(refresh_memory, [], [memory_info, memory_history])
            btn_clear_mem.click(clear_chat_pro, [], [chat, audio_out, msg_in])

        



# ======================================================================
# ============================================================
# PHẦN 12.7: LAUNCH APP PRO
# ============================================================

print('='*70)
print('🚀 KHỞI ĐỘNG ỨNG DỤNG AI PRO')
print('='*70)
print()



print('='*70)
print('  Đang tạo public link...')
print('  Link sẽ xuất hiện bên dưới sau vài giây...')
print('='*70)
print()

# Launch với demo_pro thay vì demo
demo.launch(
    share=True,
    server_name='0.0.0.0',
    show_error=True
)