matrice-analytics 0.1.60__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +146 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3291 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +660 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for the Customer Service use case.
|
|
3
|
+
|
|
4
|
+
This module tests detailed functionality for customer service analytics including:
|
|
5
|
+
- Queue management
|
|
6
|
+
- Service time analysis
|
|
7
|
+
- Staff efficiency metrics
|
|
8
|
+
- Customer flow analysis
|
|
9
|
+
- Alert generation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
from unittest.mock import patch, MagicMock
|
|
16
|
+
|
|
17
|
+
from .test_utilities import BasePostProcessingTest
|
|
18
|
+
from .test_data_generators import (
|
|
19
|
+
create_detection_results, create_tracking_results,
|
|
20
|
+
create_customer_service_config, create_zone_polygons,
|
|
21
|
+
create_performance_test_data, create_edge_case_data
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from src.matrice_analytics.post_processing import (
|
|
25
|
+
PostProcessor, ProcessingStatus, CustomerServiceConfig,
|
|
26
|
+
TrackingConfig, AlertConfig
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestCustomerServiceUseCase(BasePostProcessingTest):
|
|
31
|
+
"""Test cases for customer service use case."""
|
|
32
|
+
|
|
33
|
+
def test_basic_customer_service_processing(self):
|
|
34
|
+
"""Test basic customer service processing functionality."""
|
|
35
|
+
# Create test data with relevant categories
|
|
36
|
+
test_data = create_detection_results(
|
|
37
|
+
num_detections=10,
|
|
38
|
+
categories=["person", "staff", "customer"],
|
|
39
|
+
confidence_range=(0.6, 0.9)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Create configuration
|
|
43
|
+
config = create_customer_service_config(
|
|
44
|
+
confidence_threshold=0.5,
|
|
45
|
+
service_proximity_threshold=50.0
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Process the data
|
|
49
|
+
result = self.processor.process(test_data, config)
|
|
50
|
+
|
|
51
|
+
# Validate basic result structure
|
|
52
|
+
self.assert_processing_result_valid(result, expected_usecase="customer_service")
|
|
53
|
+
# Handle both SUCCESS and WARNING status as valid
|
|
54
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
55
|
+
|
|
56
|
+
# Check for expected metrics (using actual metric names from implementation)
|
|
57
|
+
expected_metrics = ["customer_to_staff_ratio", "service_coverage", "interaction_rate"]
|
|
58
|
+
for metric in expected_metrics:
|
|
59
|
+
self.assertIn(metric, result.metrics)
|
|
60
|
+
|
|
61
|
+
# Check that insights were generated
|
|
62
|
+
self.assert_insights_generated(result, min_insights=1)
|
|
63
|
+
|
|
64
|
+
def test_customer_service_with_areas(self):
|
|
65
|
+
"""Test customer service with defined areas."""
|
|
66
|
+
# Create test data
|
|
67
|
+
test_data = create_detection_results(
|
|
68
|
+
num_detections=8,
|
|
69
|
+
categories=["staff", "customer", "person"]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Create configuration with predefined areas
|
|
73
|
+
config = create_customer_service_config(
|
|
74
|
+
confidence_threshold=0.6,
|
|
75
|
+
service_proximity_threshold=100.0
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
result = self.processor.process(test_data, config)
|
|
79
|
+
|
|
80
|
+
# Validate result
|
|
81
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
82
|
+
|
|
83
|
+
# Check for area-related metrics (using actual metric names)
|
|
84
|
+
self.assertIn("area_utilization", result.metrics)
|
|
85
|
+
area_utilization = result.metrics["area_utilization"]
|
|
86
|
+
self.assertIsInstance(area_utilization, dict)
|
|
87
|
+
|
|
88
|
+
# Should have customer and service area analysis
|
|
89
|
+
if "customer_areas" in area_utilization:
|
|
90
|
+
self.assertIsInstance(area_utilization["customer_areas"], (int, float))
|
|
91
|
+
if "service_areas" in area_utilization:
|
|
92
|
+
self.assertIsInstance(area_utilization["service_areas"], (int, float))
|
|
93
|
+
|
|
94
|
+
def test_customer_service_with_tracking(self):
|
|
95
|
+
"""Test customer service with tracking enabled."""
|
|
96
|
+
# Create tracking data with staff and customers
|
|
97
|
+
tracking_data = create_tracking_results(
|
|
98
|
+
num_tracks=10,
|
|
99
|
+
categories=["staff", "customer", "person"]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
result = self.processor.process_simple(
|
|
103
|
+
tracking_data,
|
|
104
|
+
usecase="customer_service",
|
|
105
|
+
confidence_threshold=0.6,
|
|
106
|
+
enable_tracking=True,
|
|
107
|
+
staff_categories=["staff"],
|
|
108
|
+
customer_categories=["customer", "person"],
|
|
109
|
+
max_service_time=1200.0
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Validate result
|
|
113
|
+
self.assert_processing_result_valid(result)
|
|
114
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
115
|
+
|
|
116
|
+
# Should have customer service metrics
|
|
117
|
+
self.assertIn("customer_to_staff_ratio", result.metrics)
|
|
118
|
+
self.assertIn("service_coverage", result.metrics)
|
|
119
|
+
|
|
120
|
+
def test_customer_service_proximity_analysis(self):
|
|
121
|
+
"""Test proximity-based service analysis."""
|
|
122
|
+
# Create test data with specific positions for proximity analysis
|
|
123
|
+
test_data = create_detection_results(
|
|
124
|
+
num_detections=8,
|
|
125
|
+
categories=["staff", "customer"]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Manually adjust positions for proximity testing
|
|
129
|
+
for i, detection in enumerate(test_data):
|
|
130
|
+
if i < 4: # First half as staff
|
|
131
|
+
detection["category"] = "staff"
|
|
132
|
+
# Position staff in service area
|
|
133
|
+
detection["bbox"] = [500 + i * 20, 100, 550 + i * 20, 200]
|
|
134
|
+
else: # Second half as customers
|
|
135
|
+
detection["category"] = "customer"
|
|
136
|
+
# Position customers near staff (within proximity)
|
|
137
|
+
detection["bbox"] = [480 + (i-4) * 30, 150, 520 + (i-4) * 30, 230]
|
|
138
|
+
|
|
139
|
+
result = self.processor.process_simple(
|
|
140
|
+
test_data,
|
|
141
|
+
usecase="customer_service",
|
|
142
|
+
confidence_threshold=0.5,
|
|
143
|
+
service_proximity_threshold=100.0,
|
|
144
|
+
staff_categories=["staff"],
|
|
145
|
+
customer_categories=["customer"]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Should analyze proximity patterns
|
|
149
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
150
|
+
|
|
151
|
+
# Should have service-related metrics
|
|
152
|
+
self.assertIn("customer_to_staff_ratio", result.metrics)
|
|
153
|
+
self.assertIn("service_coverage", result.metrics)
|
|
154
|
+
|
|
155
|
+
def test_customer_service_staff_utilization(self):
|
|
156
|
+
"""Test staff utilization analysis."""
|
|
157
|
+
# Create test data with staff and customers
|
|
158
|
+
test_data = create_detection_results(
|
|
159
|
+
num_detections=15,
|
|
160
|
+
categories=["staff"] * 5 + ["customer"] * 10
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
config = create_customer_service_config(
|
|
164
|
+
confidence_threshold=0.6,
|
|
165
|
+
service_proximity_threshold=100.0
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
result = self.processor.process(test_data, config)
|
|
169
|
+
|
|
170
|
+
# Validate result (handle WARNING status)
|
|
171
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
172
|
+
|
|
173
|
+
# Check for staff utilization metrics
|
|
174
|
+
self.assertIn("staff_utilization", result.metrics)
|
|
175
|
+
self.assertIn("customer_to_staff_ratio", result.metrics)
|
|
176
|
+
|
|
177
|
+
# Check utilization values are reasonable
|
|
178
|
+
staff_utilization = result.metrics["staff_utilization"]
|
|
179
|
+
self.assertIsInstance(staff_utilization, (int, float))
|
|
180
|
+
self.assertGreaterEqual(staff_utilization, 0.0)
|
|
181
|
+
|
|
182
|
+
def test_customer_service_queue_analysis(self):
|
|
183
|
+
"""Test queue management analysis."""
|
|
184
|
+
# Create test data representing a queue scenario
|
|
185
|
+
test_data = []
|
|
186
|
+
|
|
187
|
+
# Add service counter staff
|
|
188
|
+
test_data.append({
|
|
189
|
+
"bbox": [500, 100, 550, 200],
|
|
190
|
+
"confidence": 0.9,
|
|
191
|
+
"category": "staff",
|
|
192
|
+
"category_id": 0
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
# Add customers in a queue formation
|
|
196
|
+
queue_positions = [
|
|
197
|
+
[450, 150], [400, 150], [350, 150], [300, 150], [250, 150]
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
for i, (x, y) in enumerate(queue_positions):
|
|
201
|
+
test_data.append({
|
|
202
|
+
"bbox": [x, y, x+40, y+80],
|
|
203
|
+
"confidence": 0.8,
|
|
204
|
+
"category": "customer",
|
|
205
|
+
"category_id": 1
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
result = self.processor.process_simple(
|
|
209
|
+
test_data,
|
|
210
|
+
usecase="customer_service",
|
|
211
|
+
confidence_threshold=0.5,
|
|
212
|
+
service_proximity_threshold=150.0,
|
|
213
|
+
staff_categories=["staff"],
|
|
214
|
+
customer_categories=["customer"],
|
|
215
|
+
buffer_time=2.0
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Should analyze queue patterns
|
|
219
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
220
|
+
|
|
221
|
+
# Should have customer service metrics
|
|
222
|
+
self.assertIn("customer_to_staff_ratio", result.metrics)
|
|
223
|
+
|
|
224
|
+
def test_customer_service_service_time_analysis(self):
|
|
225
|
+
"""Test service time analysis with tracking data."""
|
|
226
|
+
# Create tracking data showing service interactions over time
|
|
227
|
+
tracking_data = []
|
|
228
|
+
|
|
229
|
+
# Staff track (stationary)
|
|
230
|
+
staff_track = {
|
|
231
|
+
"track_id": 1,
|
|
232
|
+
"category": "staff",
|
|
233
|
+
"points": [],
|
|
234
|
+
"start_frame": 0,
|
|
235
|
+
"end_frame": 100,
|
|
236
|
+
"is_active": True,
|
|
237
|
+
"total_frames": 100
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Staff stays in one place
|
|
241
|
+
for frame in range(100):
|
|
242
|
+
staff_track["points"].append({
|
|
243
|
+
"frame_id": frame,
|
|
244
|
+
"track_id": 1,
|
|
245
|
+
"bbox": [500, 100, 550, 200],
|
|
246
|
+
"confidence": 0.9,
|
|
247
|
+
"category": "staff",
|
|
248
|
+
"timestamp": time.time() + frame
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
tracking_data.append(staff_track)
|
|
252
|
+
|
|
253
|
+
# Customer track (approaches staff, gets service, leaves)
|
|
254
|
+
customer_track = {
|
|
255
|
+
"track_id": 2,
|
|
256
|
+
"category": "customer",
|
|
257
|
+
"points": [],
|
|
258
|
+
"start_frame": 10,
|
|
259
|
+
"end_frame": 60,
|
|
260
|
+
"is_active": False,
|
|
261
|
+
"total_frames": 50
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Customer movement: approach -> wait -> service -> leave
|
|
265
|
+
for frame in range(50):
|
|
266
|
+
x = 300 + frame * 4 # Moving towards staff
|
|
267
|
+
if frame > 30: # After service, moving away
|
|
268
|
+
x = 530 - (frame - 30) * 4
|
|
269
|
+
|
|
270
|
+
customer_track["points"].append({
|
|
271
|
+
"frame_id": frame + 10,
|
|
272
|
+
"track_id": 2,
|
|
273
|
+
"bbox": [x, 150, x+40, 230],
|
|
274
|
+
"confidence": 0.8,
|
|
275
|
+
"category": "customer",
|
|
276
|
+
"timestamp": time.time() + frame + 10
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
tracking_data.append(customer_track)
|
|
280
|
+
|
|
281
|
+
result = self.processor.process_simple(
|
|
282
|
+
tracking_data,
|
|
283
|
+
usecase="customer_service",
|
|
284
|
+
confidence_threshold=0.5,
|
|
285
|
+
enable_tracking=True,
|
|
286
|
+
service_proximity_threshold=100.0,
|
|
287
|
+
max_service_time=1800.0,
|
|
288
|
+
staff_categories=["staff"],
|
|
289
|
+
customer_categories=["customer"]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Should analyze service times
|
|
293
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
294
|
+
|
|
295
|
+
# Should have service metrics
|
|
296
|
+
self.assertIn("customer_to_staff_ratio", result.metrics)
|
|
297
|
+
|
|
298
|
+
def test_customer_service_config_validation(self):
|
|
299
|
+
"""Test customer service configuration validation."""
|
|
300
|
+
# Valid configuration
|
|
301
|
+
valid_config = CustomerServiceConfig(
|
|
302
|
+
category="sales",
|
|
303
|
+
usecase="customer_service",
|
|
304
|
+
confidence_threshold=0.6,
|
|
305
|
+
service_proximity_threshold=100.0,
|
|
306
|
+
staff_categories=["staff", "employee"],
|
|
307
|
+
customer_categories=["customer", "person"],
|
|
308
|
+
customer_areas={"waiting": [[0, 0], [100, 0], [100, 100], [0, 100]]},
|
|
309
|
+
staff_areas={"counter": [[200, 0], [300, 0], [300, 100], [200, 100]]},
|
|
310
|
+
service_areas={"desk": [[150, 150], [250, 150], [250, 250], [150, 250]]}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
errors = valid_config.validate()
|
|
314
|
+
self.assertEqual(len(errors), 0)
|
|
315
|
+
|
|
316
|
+
# Invalid configurations
|
|
317
|
+
invalid_configs = [
|
|
318
|
+
# Invalid service proximity threshold
|
|
319
|
+
CustomerServiceConfig(
|
|
320
|
+
category="sales",
|
|
321
|
+
usecase="customer_service",
|
|
322
|
+
service_proximity_threshold=-50.0
|
|
323
|
+
),
|
|
324
|
+
# Invalid max service time
|
|
325
|
+
CustomerServiceConfig(
|
|
326
|
+
category="sales",
|
|
327
|
+
usecase="customer_service",
|
|
328
|
+
max_service_time=-100.0
|
|
329
|
+
),
|
|
330
|
+
# Empty staff categories
|
|
331
|
+
CustomerServiceConfig(
|
|
332
|
+
category="sales",
|
|
333
|
+
usecase="customer_service",
|
|
334
|
+
staff_categories=[]
|
|
335
|
+
),
|
|
336
|
+
# Empty customer categories
|
|
337
|
+
CustomerServiceConfig(
|
|
338
|
+
category="sales",
|
|
339
|
+
usecase="customer_service",
|
|
340
|
+
customer_categories=[]
|
|
341
|
+
),
|
|
342
|
+
# Invalid area polygon
|
|
343
|
+
CustomerServiceConfig(
|
|
344
|
+
category="sales",
|
|
345
|
+
usecase="customer_service",
|
|
346
|
+
customer_areas={"invalid": [[0, 0], [100, 100]]} # Only 2 points
|
|
347
|
+
)
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
for config in invalid_configs:
|
|
351
|
+
errors = config.validate()
|
|
352
|
+
self.assertGreater(len(errors), 0)
|
|
353
|
+
|
|
354
|
+
def test_customer_service_tracking_config_validation(self):
|
|
355
|
+
"""Test tracking configuration validation."""
|
|
356
|
+
# Valid tracking configuration
|
|
357
|
+
valid_tracking_config = TrackingConfig(
|
|
358
|
+
tracking_method="kalman",
|
|
359
|
+
max_age=30,
|
|
360
|
+
min_hits=3,
|
|
361
|
+
iou_threshold=0.3,
|
|
362
|
+
target_classes=["staff", "customer"]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
errors = valid_tracking_config.validate()
|
|
366
|
+
self.assertEqual(len(errors), 0)
|
|
367
|
+
|
|
368
|
+
# Invalid tracking configurations
|
|
369
|
+
invalid_tracking_configs = [
|
|
370
|
+
# Invalid tracking method
|
|
371
|
+
TrackingConfig(tracking_method="invalid_method"),
|
|
372
|
+
# Invalid max_age
|
|
373
|
+
TrackingConfig(max_age=-5),
|
|
374
|
+
# Invalid min_hits
|
|
375
|
+
TrackingConfig(min_hits=0),
|
|
376
|
+
# Invalid IoU threshold
|
|
377
|
+
TrackingConfig(iou_threshold=1.5)
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
for config in invalid_tracking_configs:
|
|
381
|
+
errors = config.validate()
|
|
382
|
+
self.assertGreater(len(errors), 0)
|
|
383
|
+
|
|
384
|
+
def test_customer_service_alerts(self):
|
|
385
|
+
"""Test customer service alert generation."""
|
|
386
|
+
# Create test data that should trigger alerts
|
|
387
|
+
test_data = create_detection_results(
|
|
388
|
+
num_detections=20,
|
|
389
|
+
categories=["person", "customer"], # High customer count, low staff
|
|
390
|
+
confidence_range=(0.7, 0.9)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Create configuration with alert settings
|
|
394
|
+
config = create_customer_service_config(
|
|
395
|
+
confidence_threshold=0.6,
|
|
396
|
+
service_proximity_threshold=30.0
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Process the data
|
|
400
|
+
result = self.processor.process(test_data, config)
|
|
401
|
+
|
|
402
|
+
# Validate result
|
|
403
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
404
|
+
|
|
405
|
+
# Check for optimization opportunities instead of alerts
|
|
406
|
+
self.assertIn("optimization_opportunities", result.metrics)
|
|
407
|
+
opportunities = result.metrics["optimization_opportunities"]
|
|
408
|
+
self.assertIsInstance(opportunities, list)
|
|
409
|
+
|
|
410
|
+
# Should have suggestions for high customer to staff ratio
|
|
411
|
+
self.assertGreater(len(opportunities), 0)
|
|
412
|
+
|
|
413
|
+
def test_customer_service_empty_data(self):
|
|
414
|
+
"""Test customer service with empty input data."""
|
|
415
|
+
result = self.processor.process_simple(
|
|
416
|
+
[],
|
|
417
|
+
usecase="customer_service",
|
|
418
|
+
confidence_threshold=0.6
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Should handle empty data gracefully (may return WARNING for empty data)
|
|
422
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
423
|
+
|
|
424
|
+
# Check for appropriate summary message
|
|
425
|
+
self.assertTrue(
|
|
426
|
+
"no" in result.summary.lower() or
|
|
427
|
+
"empty" in result.summary.lower() or
|
|
428
|
+
"zero" in result.summary.lower()
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Should have basic metrics structure
|
|
432
|
+
self.assertIsInstance(result.metrics, dict)
|
|
433
|
+
|
|
434
|
+
def test_customer_service_performance(self):
|
|
435
|
+
"""Test customer service performance with large datasets."""
|
|
436
|
+
# Create large dataset with mixed categories
|
|
437
|
+
large_data = create_detection_results(
|
|
438
|
+
num_detections=500,
|
|
439
|
+
categories=["staff", "customer", "person", "employee"]
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
start_time = time.time()
|
|
443
|
+
result = self.processor.process_simple(
|
|
444
|
+
large_data,
|
|
445
|
+
usecase="customer_service",
|
|
446
|
+
confidence_threshold=0.6,
|
|
447
|
+
service_proximity_threshold=100.0
|
|
448
|
+
)
|
|
449
|
+
processing_time = time.time() - start_time
|
|
450
|
+
|
|
451
|
+
# Should complete successfully (may return WARNING for large datasets)
|
|
452
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
453
|
+
|
|
454
|
+
# Should complete within reasonable time (3 seconds for complex analysis)
|
|
455
|
+
self.assertLess(processing_time, 3.0)
|
|
456
|
+
|
|
457
|
+
# Should have reasonable performance metrics (processing_time may be 0 for fast processing)
|
|
458
|
+
self.assertGreaterEqual(result.processing_time, 0)
|
|
459
|
+
|
|
460
|
+
def test_customer_service_insights_generation(self):
|
|
461
|
+
"""Test that meaningful insights are generated."""
|
|
462
|
+
# Create test data with staff-customer interactions
|
|
463
|
+
test_data = create_detection_results(
|
|
464
|
+
num_detections=25,
|
|
465
|
+
categories=["staff"] * 5 + ["customer"] * 20
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
config = create_customer_service_config(
|
|
469
|
+
confidence_threshold=0.6,
|
|
470
|
+
service_proximity_threshold=100.0
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
result = self.processor.process(test_data, config)
|
|
474
|
+
|
|
475
|
+
# Should generate multiple insights
|
|
476
|
+
self.assert_insights_generated(result, min_insights=2)
|
|
477
|
+
|
|
478
|
+
# Check insight quality
|
|
479
|
+
insights = result.insights
|
|
480
|
+
|
|
481
|
+
# Should mention staff and customers
|
|
482
|
+
staff_mentioned = any("staff" in insight.lower() for insight in insights)
|
|
483
|
+
customer_mentioned = any("customer" in insight.lower() for insight in insights)
|
|
484
|
+
|
|
485
|
+
self.assertTrue(staff_mentioned or customer_mentioned)
|
|
486
|
+
|
|
487
|
+
# Should mention service aspects
|
|
488
|
+
service_mentioned = any("service" in insight.lower() for insight in insights)
|
|
489
|
+
self.assertTrue(service_mentioned)
|
|
490
|
+
|
|
491
|
+
def test_customer_service_metrics_completeness(self):
|
|
492
|
+
"""Test that all expected metrics are generated."""
|
|
493
|
+
# Create test data
|
|
494
|
+
test_data = create_detection_results(
|
|
495
|
+
num_detections=15,
|
|
496
|
+
categories=["staff", "customer", "person"]
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# Create comprehensive configuration
|
|
500
|
+
config = create_customer_service_config(
|
|
501
|
+
confidence_threshold=0.6,
|
|
502
|
+
enable_tracking=True,
|
|
503
|
+
service_proximity_threshold=100.0
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
result = self.processor.process(test_data, config)
|
|
507
|
+
|
|
508
|
+
# Handle WARNING status as valid for this test
|
|
509
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
510
|
+
|
|
511
|
+
# Check for core metrics (using actual metric names from implementation)
|
|
512
|
+
expected_core_metrics = [
|
|
513
|
+
"customer_to_staff_ratio",
|
|
514
|
+
"service_coverage",
|
|
515
|
+
"interaction_rate",
|
|
516
|
+
"staff_utilization",
|
|
517
|
+
"area_utilization",
|
|
518
|
+
"service_quality_score",
|
|
519
|
+
"attention_score",
|
|
520
|
+
"overall_performance"
|
|
521
|
+
]
|
|
522
|
+
|
|
523
|
+
for metric in expected_core_metrics:
|
|
524
|
+
self.assertIn(metric, result.metrics)
|
|
525
|
+
|
|
526
|
+
# Check for optimization opportunities
|
|
527
|
+
self.assertIn("optimization_opportunities", result.metrics)
|
|
528
|
+
self.assertIsInstance(result.metrics["optimization_opportunities"], list)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class TestCustomerServiceIntegration(BasePostProcessingTest):
|
|
532
|
+
"""Integration tests for customer service with other components."""
|
|
533
|
+
|
|
534
|
+
def test_customer_service_config_serialization(self):
|
|
535
|
+
"""Test configuration serialization and deserialization."""
|
|
536
|
+
# Create configuration
|
|
537
|
+
original_config = create_customer_service_config(
|
|
538
|
+
confidence_threshold=0.7,
|
|
539
|
+
service_proximity_threshold=120.0
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# Save and load configuration
|
|
543
|
+
config_file = self.create_temp_config_file(original_config)
|
|
544
|
+
loaded_config = self.processor.load_config(config_file)
|
|
545
|
+
|
|
546
|
+
# Compare key attributes
|
|
547
|
+
self.assertEqual(original_config.usecase, loaded_config.usecase)
|
|
548
|
+
self.assertEqual(original_config.category, loaded_config.category)
|
|
549
|
+
self.assertEqual(original_config.confidence_threshold, loaded_config.confidence_threshold)
|
|
550
|
+
self.assertEqual(original_config.service_proximity_threshold, loaded_config.service_proximity_threshold)
|
|
551
|
+
self.assertEqual(original_config.staff_categories, loaded_config.staff_categories)
|
|
552
|
+
self.assertEqual(original_config.customer_categories, loaded_config.customer_categories)
|
|
553
|
+
|
|
554
|
+
def test_customer_service_error_recovery(self):
|
|
555
|
+
"""Test error recovery in customer service."""
|
|
556
|
+
# Create partially invalid data
|
|
557
|
+
mixed_quality_data = [
|
|
558
|
+
# Valid staff detection
|
|
559
|
+
{"bbox": [10, 10, 50, 100], "confidence": 0.9, "category": "staff"},
|
|
560
|
+
# Valid customer detection
|
|
561
|
+
{"bbox": [100, 100, 140, 200], "confidence": 0.8, "category": "customer"},
|
|
562
|
+
# Invalid detection
|
|
563
|
+
{"bbox": [200, 200, 190, 190], "confidence": 0.9, "category": "person"}, # Invalid bbox
|
|
564
|
+
# Another valid detection
|
|
565
|
+
{"bbox": [300, 300, 340, 400], "confidence": 0.7, "category": "customer"}
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
result = self.processor.process_simple(
|
|
569
|
+
mixed_quality_data,
|
|
570
|
+
usecase="customer_service",
|
|
571
|
+
confidence_threshold=0.5,
|
|
572
|
+
staff_categories=["staff"],
|
|
573
|
+
customer_categories=["customer", "person"]
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Should handle partial failures gracefully
|
|
577
|
+
self.assertIn(result.status, [ProcessingStatus.SUCCESS, ProcessingStatus.WARNING])
|
|
578
|
+
|
|
579
|
+
# Should still provide some results
|
|
580
|
+
self.assertIsNotNone(result.data)
|
|
581
|
+
self.assertIsInstance(result.metrics, dict)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
if __name__ == "__main__":
|
|
585
|
+
unittest.main(verbosity=2)
|