predict_caLamviec_nhansu/extract_25_features.py

440 lines
18 KiB
Python

"""
EXTRACTION FUNCTION: 25 KEYWORD FEATURES TỪ TASKS TEXT
Dựa trên phân tích 30,917 công việc từ 302 tòa nhà
Updated: January 5, 2026
"""
import pandas as pd
import re
from typing import Dict, List
def extract_25_keyword_features(tasks_text: str) -> Dict[str, float]:
"""
Trích xuất 25 keyword-based features từ tasks text
Args:
tasks_text: Chuỗi text chứa tất cả công việc (all_task_normal + all_task_dinhky)
Returns:
Dict với 25 features
"""
# Xử lý missing values
if pd.isna(tasks_text) or str(tasks_text).strip() == '':
return _get_empty_features()
# Chuyển về lowercase và tách tasks
tasks_text = str(tasks_text).lower()
# Tách tasks bằng các delimiter: ; | hoặc xuống dòng
tasks = re.split(r'[;|\n]+', tasks_text)
tasks = [t.strip() for t in tasks if t.strip()]
# =================================================================
# NHÓM 1: TASK COUNTS THEO LOẠI CÔNG VIỆC (9 features)
# =================================================================
# 1. Tổng số công việc
num_tasks = len(tasks)
# 2. Vệ sinh thường ngày (55.9% dữ liệu)
cleaning_keywords = [
'vệ sinh', 'tvs', 'tổng vệ sinh', 'lau', 'chùi', 'quét', 'hút',
'đẩy khô', 'lau ẩm', 'đẩy ẩm', 'làm sạch', 'lau bụi', 'quét bụi',
'lau kính', 'lau sàn', 'quét sàn', 'hút bụi', 'lau nền'
]
num_cleaning_tasks = _count_tasks_with_keywords(tasks, cleaning_keywords)
# 3. Thu gom/thay rác (7.9% dữ liệu) - MỚI ⭐
trash_keywords = [
'thu gom rác', 'thay rác', 'vận chuyển rác', 'tua rác', 'đổ rác',
'thu rác', 'gom rác', 'chuyển rác', 'bỏ rác', 'đẩy rác',
'quét rác nổi', 'trực rác', 'rác nổi'
]
num_trash_collection_tasks = _count_tasks_with_keywords(tasks, trash_keywords)
# 4. Trực/kiểm tra phát sinh (16.1% dữ liệu) - MỚI ⭐
monitoring_keywords = [
'trực', 'trực phát sinh', 'trực lại', 'trực ps', 'trực tua',
'kiểm tra', 'check', 'giám sát', 'theo dõi', 'tuần tra'
]
num_monitoring_tasks = _count_tasks_with_keywords(tasks, monitoring_keywords)
# 5. Dọn phòng Y TẾ (0.4% nhưng đặc trưng) - MỚI ⭐
room_cleaning_keywords = [
'dọn mổ', 'dọn đẻ', 'dọn can thiệp', 'ra viện', 'dọn phòng',
'bệnh nhân ra viện', 'dọn khi bệnh nhân', 'dọn phòng bệnh'
]
num_room_cleaning_tasks = _count_tasks_with_keywords(tasks, room_cleaning_keywords)
# 6. Vệ sinh chuyên sâu (4.5% dữ liệu) - MỚI ⭐
deep_cleaning_keywords = [
'cọ rửa', 'cọ bồn cầu', 'cọ', 'gạt kính', 'gạt', 'đánh sàn',
'đánh chân tường', 'đánh cọ', 'đánh vết bẩn', 'chà tường',
'đánh dép', 'cọ gương', 'cọ lavabo', 'cọ thùng rác'
]
num_deep_cleaning_tasks = _count_tasks_with_keywords(tasks, deep_cleaning_keywords)
# 7. Bảo trì/sửa chữa (0.6% dữ liệu)
maintenance_keywords = [
'bảo dưỡng', 'sửa chữa', 'bảo trì', 'thay thế', 'sửa',
'thay', 'kiểm định', 'bảo dưỡng máy'
]
num_maintenance_tasks = _count_tasks_with_keywords(tasks, maintenance_keywords)
# 8. Hỗ trợ (5.8% dữ liệu) - MỚI ⭐
support_keywords = [
'giao ca', 'bàn giao', 'bàn giao ca', 'chụp ảnh', 'nhận ca',
'vsdc', 'vệ sinh dụng cụ', 'chuẩn bị dụng cụ', 'vệ sinh xe đồ',
'chuẩn bị nước', 'chuẩn bị', 'giao ban'
]
num_support_tasks = _count_tasks_with_keywords(tasks, support_keywords)
# 9. Công việc khác (không thuộc các loại trên)
# Tạm tính = tổng - (các loại đã đếm, nhưng lưu ý có overlap)
# Để tính chính xác, cần đếm tasks không match bất kỳ keyword nào
all_keywords = (cleaning_keywords + trash_keywords + monitoring_keywords +
room_cleaning_keywords + deep_cleaning_keywords +
maintenance_keywords + support_keywords)
num_other_tasks = _count_tasks_without_keywords(tasks, all_keywords)
# =================================================================
# NHÓM 2: AREA COVERAGE - KHU VỰC (10 features)
# =================================================================
# 10. WC/Nhà vệ sinh (20.4% dữ liệu)
wc_keywords = [
'wc', 'toilet', 'nhà vệ sinh', 'restroom', 'phòng vệ sinh',
'bồn cầu', 'lavabo', 'tiểu nam', 'bồn tiểu', 'wc công cộng',
'wc nhân viên', 'nhà wc'
]
num_wc_tasks = _count_tasks_with_keywords(tasks, wc_keywords)
# 11. Hành lang (13.7% dữ liệu)
hallway_keywords = [
'hành lang', 'corridor', 'lối đi', 'hall', 'hành lang tầng',
'hl', 'hanh lang'
]
num_hallway_tasks = _count_tasks_with_keywords(tasks, hallway_keywords)
# 12. Sảnh (7.6% dữ liệu)
lobby_keywords = [
'sảnh', 'lobby', 'tiền sảnh', 'sảnh đỏ', 'sảnh chính',
'tiền sảnh', 'sảnh tầng', 'sanh'
]
num_lobby_tasks = _count_tasks_with_keywords(tasks, lobby_keywords)
# 13. Phòng bệnh Y TẾ (1.5% dữ liệu) - MỚI ⭐
patient_room_keywords = [
'phòng bệnh', 'giường bệnh', 'phòng víp', 'phòng vip',
'phòng bệnh nhân', 'pb', 'phòng bv'
]
num_patient_room_tasks = _count_tasks_with_keywords(tasks, patient_room_keywords)
# 14. Phòng khám Y TẾ (0.3% dữ liệu) - MỚI ⭐
clinic_room_keywords = [
'phòng khám', 'khoa khám', 'phòng nội', 'phòng sản',
'phòng khám bệnh', 'khu khám', 'pk'
]
num_clinic_room_tasks = _count_tasks_with_keywords(tasks, clinic_room_keywords)
# 15. Phòng mổ Y TẾ (0.4% dữ liệu) - MỚI ⭐
surgery_room_keywords = [
'phòng mổ', 'hậu phẫu', 'phòng phẫu thuật', 'khu mổ',
'phòng pt', 'ngoài phòng mổ', 'trong phòng mổ'
]
num_surgery_room_tasks = _count_tasks_with_keywords(tasks, surgery_room_keywords)
# 16. Ngoại cảnh (4.3% dữ liệu)
outdoor_keywords = [
'ngoại cảnh', 'sân', 'vỉa hè', 'khuôn viên', 'cổng',
'outdoor', 'bãi xe', 'tầng hầm', 'sân sau', 'sân trước'
]
num_outdoor_tasks = _count_tasks_with_keywords(tasks, outdoor_keywords)
# 17. Thang máy/Cầu thang (10.6% dữ liệu)
elevator_keywords = [
'thang máy', 'elevator', 'lift', 'cầu thang', 'bậc tam cấp',
'thang bộ', 'cầu thang bộ', 'tay vịn', 'tam cấp'
]
num_elevator_tasks = _count_tasks_with_keywords(tasks, elevator_keywords)
# 18. Phòng nhân viên/hành chính (4.4% dữ liệu) - MỚI ⭐
office_keywords = [
'phòng nhân viên', 'phòng giám đốc', 'phòng họp', 'phòng hành chính',
'văn phòng', 'phòng gd', 'phòng pgd', 'phòng ban', 'phòng giao ban',
'phòng bác sĩ', 'phòng trưởng khoa', 'hội trường', 'phòng kế toán'
]
num_office_tasks = _count_tasks_with_keywords(tasks, office_keywords)
# 19. Phòng kỹ thuật Y TẾ (0.2% dữ liệu) - MỚI ⭐
technical_room_keywords = [
'phòng xét nghiệm', 'phòng chụp', 'xq', 'siêu âm', 'kho dược',
'phòng xn', 'labo', 'phòng thí nghiệm', 'phòng kỹ thuật',
'phòng điện tim', 'phòng nội soi', 'phòng cấp cứu', 'phòng hồi sức'
]
num_technical_room_tasks = _count_tasks_with_keywords(tasks, technical_room_keywords)
# =================================================================
# NHÓM 3: RATIOS & COMPLEXITY (6 features)
# =================================================================
# 20. Tỷ lệ vệ sinh thường ngày
cleaning_ratio = num_cleaning_tasks / num_tasks if num_tasks > 0 else 0.0
# 21. Tỷ lệ thu gom rác - MỚI ⭐
trash_collection_ratio = num_trash_collection_tasks / num_tasks if num_tasks > 0 else 0.0
# 22. Tỷ lệ trực/kiểm tra - MỚI ⭐
monitoring_ratio = num_monitoring_tasks / num_tasks if num_tasks > 0 else 0.0
# 23. Tỷ lệ dọn phòng Y TẾ - MỚI ⭐
room_cleaning_ratio = num_room_cleaning_tasks / num_tasks if num_tasks > 0 else 0.0
# 24. Độ đa dạng khu vực (0-10)
area_counts = [
num_wc_tasks, num_hallway_tasks, num_lobby_tasks,
num_patient_room_tasks, num_clinic_room_tasks, num_surgery_room_tasks,
num_outdoor_tasks, num_elevator_tasks, num_office_tasks, num_technical_room_tasks
]
area_diversity = sum(1 for count in area_counts if count > 0)
# 25. Điểm phức tạp (0.0 - 10.0) - MỚI ⭐
# Dựa vào:
# - Độ dài text (càng dài càng phức tạp)
# - Số lượng công việc (càng nhiều càng phức tạp)
# - Từ khóa kỹ thuật (Y TẾ, máy móc...)
technical_keywords = [
'bms', 'hvac', 'camera', 'access control', 'máy phát', 'máy móc',
'hệ thống', 'thiết bị', 'bảo dưỡng máy', 'sửa máy', 'kiểm tra máy',
'xét nghiệm', 'chụp chiếu', 'điện tim', 'nội soi', 'phẫu thuật'
]
num_technical_keywords = _count_tasks_with_keywords(tasks, technical_keywords)
# Công thức tính task_complexity_score:
# - Text length: 0-3 điểm (0-1000 chars = 0, 1000-5000 = 1-2, 5000+ = 3)
# - Num tasks: 0-3 điểm (0-10 = 0-1, 10-50 = 1-2, 50+ = 2-3)
# - Technical keywords: 0-2 điểm (0 = 0, 1-3 = 1, 4+ = 2)
# - Area diversity: 0-2 điểm (0-3 = 0-1, 4-7 = 1-1.5, 8-10 = 1.5-2)
text_length = len(tasks_text)
length_score = min(3.0, text_length / 2000) # Max 3.0
tasks_score = min(3.0, num_tasks / 20) # Max 3.0
technical_score = min(2.0, num_technical_keywords / 2) # Max 2.0
diversity_score = min(2.0, area_diversity / 5) # Max 2.0
task_complexity_score = round(length_score + tasks_score + technical_score + diversity_score, 2)
# =================================================================
# TRẢ VỀ DICT VỚI 25 FEATURES
# =================================================================
return {
# NHÓM 1: Task Counts (9 features)
'num_tasks': num_tasks,
'num_cleaning_tasks': num_cleaning_tasks,
'num_trash_collection_tasks': num_trash_collection_tasks,
'num_monitoring_tasks': num_monitoring_tasks,
'num_room_cleaning_tasks': num_room_cleaning_tasks,
'num_deep_cleaning_tasks': num_deep_cleaning_tasks,
'num_maintenance_tasks': num_maintenance_tasks,
'num_support_tasks': num_support_tasks,
'num_other_tasks': num_other_tasks,
# NHÓM 2: Area Coverage (10 features)
'num_wc_tasks': num_wc_tasks,
'num_hallway_tasks': num_hallway_tasks,
'num_lobby_tasks': num_lobby_tasks,
'num_patient_room_tasks': num_patient_room_tasks,
'num_clinic_room_tasks': num_clinic_room_tasks,
'num_surgery_room_tasks': num_surgery_room_tasks,
'num_outdoor_tasks': num_outdoor_tasks,
'num_elevator_tasks': num_elevator_tasks,
'num_office_tasks': num_office_tasks,
'num_technical_room_tasks': num_technical_room_tasks,
# NHÓM 3: Ratios & Complexity (6 features)
'cleaning_ratio': round(cleaning_ratio, 4),
'trash_collection_ratio': round(trash_collection_ratio, 4),
'monitoring_ratio': round(monitoring_ratio, 4),
'room_cleaning_ratio': round(room_cleaning_ratio, 4),
'area_diversity': area_diversity,
'task_complexity_score': task_complexity_score
}
def _count_tasks_with_keywords(tasks: List[str], keywords: List[str]) -> int:
"""Đếm số tasks chứa ít nhất 1 keyword"""
count = 0
for task in tasks:
task_lower = task.lower()
if any(keyword in task_lower for keyword in keywords):
count += 1
return count
def _count_tasks_without_keywords(tasks: List[str], all_keywords: List[str]) -> int:
"""Đếm số tasks KHÔNG chứa bất kỳ keyword nào"""
count = 0
for task in tasks:
task_lower = task.lower()
if not any(keyword in task_lower for keyword in all_keywords):
count += 1
return count
def _get_empty_features() -> Dict[str, float]:
"""Trả về dict với tất cả features = 0 (cho missing data)"""
return {
# NHÓM 1
'num_tasks': 0,
'num_cleaning_tasks': 0,
'num_trash_collection_tasks': 0,
'num_monitoring_tasks': 0,
'num_room_cleaning_tasks': 0,
'num_deep_cleaning_tasks': 0,
'num_maintenance_tasks': 0,
'num_support_tasks': 0,
'num_other_tasks': 0,
# NHÓM 2
'num_wc_tasks': 0,
'num_hallway_tasks': 0,
'num_lobby_tasks': 0,
'num_patient_room_tasks': 0,
'num_clinic_room_tasks': 0,
'num_surgery_room_tasks': 0,
'num_outdoor_tasks': 0,
'num_elevator_tasks': 0,
'num_office_tasks': 0,
'num_technical_room_tasks': 0,
# NHÓM 3
'cleaning_ratio': 0.0,
'trash_collection_ratio': 0.0,
'monitoring_ratio': 0.0,
'room_cleaning_ratio': 0.0,
'area_diversity': 0,
'task_complexity_score': 0.0
}
# =================================================================
# MAIN: ÁP DỤNG CHO TẤT CẢ TÒA NHÀ
# =================================================================
if __name__ == '__main__':
print("=" * 100)
print("TRÍCH XUẤT 25 KEYWORD FEATURES TỪ TASKS TEXT")
print("=" * 100)
# Đọc file
print("\n📂 Đọc file ket_qua_cong_viec_full.xlsx...")
df = pd.read_excel('ket_qua_cong_viec_full.xlsx')
print(f" ✅ Đọc thành công {len(df)} tòa nhà")
# Gộp all_task_normal và all_task_dinhky
print("\n🔗 Gộp all_task_normal + all_task_dinhky...")
df['all_tasks_combined'] = (
df['all_task_normal'].fillna('') + ' ; ' + df['all_task_dinhky'].fillna('')
)
# Áp dụng extraction function cho tất cả tòa
print("\n⚙️ Trích xuất 25 features cho từng tòa...")
features_list = []
for idx, row in df.iterrows():
if (idx + 1) % 50 == 0:
print(f" Đang xử lý... {idx + 1}/{len(df)} tòa")
features = extract_25_keyword_features(row['all_tasks_combined'])
features['ma_dia_diem'] = row['ma_dia_diem']
features_list.append(features)
print(f" ✅ Hoàn thành {len(df)} tòa")
# Tạo DataFrame
print("\n📊 Tạo DataFrame với 25 features...")
df_features = pd.DataFrame(features_list)
# Sắp xếp lại cột: ma_dia_diem đầu tiên
cols = ['ma_dia_diem'] + [col for col in df_features.columns if col != 'ma_dia_diem']
df_features = df_features[cols]
# Hiển thị thống kê
print("\n" + "=" * 100)
print("📈 THỐNG KÊ 25 FEATURES")
print("=" * 100)
print("\n🔹 NHÓM 1: TASK COUNTS (9 features)")
group1_cols = [
'num_tasks', 'num_cleaning_tasks', 'num_trash_collection_tasks',
'num_monitoring_tasks', 'num_room_cleaning_tasks', 'num_deep_cleaning_tasks',
'num_maintenance_tasks', 'num_support_tasks', 'num_other_tasks'
]
print(df_features[group1_cols].describe().round(2))
print("\n🔹 NHÓM 2: AREA COVERAGE (10 features)")
group2_cols = [
'num_wc_tasks', 'num_hallway_tasks', 'num_lobby_tasks',
'num_patient_room_tasks', 'num_clinic_room_tasks', 'num_surgery_room_tasks',
'num_outdoor_tasks', 'num_elevator_tasks', 'num_office_tasks', 'num_technical_room_tasks'
]
print(df_features[group2_cols].describe().round(2))
print("\n🔹 NHÓM 3: RATIOS & COMPLEXITY (6 features)")
group3_cols = [
'cleaning_ratio', 'trash_collection_ratio', 'monitoring_ratio',
'room_cleaning_ratio', 'area_diversity', 'task_complexity_score'
]
print(df_features[group3_cols].describe().round(4))
# Top 10 tòa có nhiều công việc nhất
print("\n" + "=" * 100)
print("🏆 TOP 10 TÒA CÓ NHIỀU CÔNG VIỆC NHẤT")
print("=" * 100)
top10 = df_features.nlargest(10, 'num_tasks')[
['ma_dia_diem', 'num_tasks', 'cleaning_ratio', 'area_diversity', 'task_complexity_score']
]
print(top10.to_string(index=False))
# Top 10 tòa phức tạp nhất
print("\n" + "=" * 100)
print("🎯 TOP 10 TÒA PHỨC TẠP NHẤT (task_complexity_score)")
print("=" * 100)
top10_complex = df_features.nlargest(10, 'task_complexity_score')[
['ma_dia_diem', 'num_tasks', 'task_complexity_score', 'area_diversity']
]
print(top10_complex.to_string(index=False))
# Lưu file
output_file = 'features_25_keywords.csv'
print(f"\n💾 Lưu features vào file: {output_file}")
df_features.to_csv(output_file, index=False, encoding='utf-8-sig')
print(f" ✅ Đã lưu {len(df_features)} tòa với 25 features")
# Hiển thị 5 tòa đầu tiên
print("\n" + "=" * 100)
print("📋 MẪU DỮ LIỆU (5 tòa đầu tiên)")
print("=" * 100)
print(df_features.head().to_string(index=False))
print("\n" + "=" * 100)
print("✅ HOÀN THÀNH!")
print("=" * 100)
print(f"\n📊 Tổng kết:")
print(f" - Số tòa nhà: {len(df_features)}")
print(f" - Số features: {len(df_features.columns) - 1}") # Trừ cột ma_dia_diem
print(f" - File output: {output_file}")
print(f"\n🎯 Bước tiếp theo:")
print(f" 1. Kiểm tra file {output_file}")
print(f" 2. Phân tích correlation giữa các features")
print(f" 3. Visualize distribution")
print(f" 4. Tiếp tục với TF-IDF features (10 features)")
print(f" 5. Join với building features (18 features)")
print(f" → TỔNG: 25 + 10 + 18 = 53 features")