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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
@@ -0,0 +1,852 @@
1
+ """
2
+ Tests for post processing configuration system.
3
+
4
+ This module tests all configuration classes, validation, serialization,
5
+ ConfigManager functionality, and file-based configuration management.
6
+ """
7
+
8
+ import unittest
9
+ import json
10
+ import yaml
11
+ import tempfile
12
+ import os
13
+ from typing import Dict, List, Any
14
+
15
+ # Fix imports for proper module resolution
16
+ import sys
17
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
18
+
19
+ from src.matrice_analytics.post_processing.core.config import (
20
+ BaseConfig, PeopleCountingConfig, CustomerServiceConfig,
21
+ ZoneConfig, TrackingConfig, AlertConfig, ConfigManager, config_manager,
22
+ ConfigValidationError
23
+ )
24
+ from src.matrice_analytics.post_processing.usecases.basic_counting_tracking import BasicCountingTrackingConfig
25
+
26
+ from .test_utilities import BasePostProcessingTest
27
+ from .test_data_generators import create_zone_polygons, create_customer_service_areas
28
+
29
+
30
+ class TestBaseConfig(BasePostProcessingTest):
31
+ """Test BaseConfig functionality."""
32
+
33
+ def test_base_config_creation(self):
34
+ """Test basic configuration creation."""
35
+ config = BaseConfig(
36
+ category="test",
37
+ usecase="test_usecase",
38
+ confidence_threshold=0.7
39
+ )
40
+
41
+ self.assertEqual(config.category, "test")
42
+ self.assertEqual(config.usecase, "test_usecase")
43
+ self.assertEqual(config.confidence_threshold, 0.7)
44
+ self.assertEqual(config.version, "1.0")
45
+
46
+ def test_base_config_validation(self):
47
+ """Test base configuration validation."""
48
+ # Valid config
49
+ valid_config = BaseConfig(
50
+ category="test",
51
+ usecase="test_usecase",
52
+ confidence_threshold=0.7
53
+ )
54
+
55
+ errors = valid_config.validate()
56
+ self.assertEqual(len(errors), 0)
57
+
58
+ # Invalid confidence threshold
59
+ invalid_config = BaseConfig(
60
+ category="test",
61
+ usecase="test_usecase",
62
+ confidence_threshold=1.5 # > 1.0
63
+ )
64
+
65
+ errors = invalid_config.validate()
66
+ self.assertGreater(len(errors), 0)
67
+ self.assertTrue(any("confidence_threshold" in error for error in errors))
68
+
69
+ # Negative confidence threshold
70
+ invalid_config2 = BaseConfig(
71
+ category="test",
72
+ usecase="test_usecase",
73
+ confidence_threshold=-0.1
74
+ )
75
+
76
+ errors = invalid_config2.validate()
77
+ self.assertGreater(len(errors), 0)
78
+
79
+ def test_base_config_serialization(self):
80
+ """Test configuration serialization."""
81
+ config = BaseConfig(
82
+ category="test",
83
+ usecase="test_usecase",
84
+ confidence_threshold=0.7,
85
+ enable_tracking=True
86
+ )
87
+
88
+ # Test to_dict
89
+ config_dict = config.to_dict()
90
+ self.assertIsInstance(config_dict, dict)
91
+ self.assertEqual(config_dict["category"], "test")
92
+ self.assertEqual(config_dict["confidence_threshold"], 0.7)
93
+ self.assertEqual(config_dict["enable_tracking"], True)
94
+
95
+ # Test from_dict
96
+ restored_config = BaseConfig.from_dict(config_dict)
97
+ self.assertEqual(restored_config.category, config.category)
98
+ self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
99
+ self.assertEqual(restored_config.enable_tracking, config.enable_tracking)
100
+
101
+ def test_base_config_json_serialization(self):
102
+ """Test JSON serialization."""
103
+ config = BaseConfig(
104
+ category="test",
105
+ usecase="test_usecase",
106
+ confidence_threshold=0.7
107
+ )
108
+
109
+ # Test to_json
110
+ json_str = config.to_json()
111
+ self.assertIsInstance(json_str, str)
112
+
113
+ # Should be valid JSON
114
+ parsed = json.loads(json_str)
115
+ self.assertEqual(parsed["category"], "test")
116
+
117
+ # Test from_json
118
+ restored_config = BaseConfig.from_json(json_str)
119
+ self.assertEqual(restored_config.category, config.category)
120
+ self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
121
+
122
+ def test_config_inheritance(self):
123
+ """Test configuration parameter inheritance."""
124
+ base_config = BaseConfig(
125
+ category="test",
126
+ usecase="test_usecase",
127
+ confidence_threshold=0.6,
128
+ enable_tracking=True
129
+ )
130
+
131
+ # Override some parameters
132
+ child_config = BaseConfig(
133
+ category="test",
134
+ usecase="test_usecase",
135
+ confidence_threshold=0.8, # Override
136
+ enable_tracking=True, # Keep same
137
+ enable_counting=True # New parameter
138
+ )
139
+
140
+ self.assertEqual(child_config.confidence_threshold, 0.8)
141
+ self.assertEqual(child_config.enable_tracking, True)
142
+ self.assertEqual(child_config.enable_counting, True)
143
+
144
+
145
+ class TestPeopleCountingConfig(BasePostProcessingTest):
146
+ """Test PeopleCountingConfig functionality."""
147
+
148
+ def test_people_counting_config_creation(self):
149
+ """Test people counting configuration creation."""
150
+ zones = create_zone_polygons(["entrance", "lobby", "exit"])
151
+
152
+ config = PeopleCountingConfig(
153
+ confidence_threshold=0.6,
154
+ enable_tracking=True,
155
+ enable_unique_counting=True,
156
+ time_window_minutes=30,
157
+ person_categories=["person", "people"],
158
+ zones=zones
159
+ )
160
+
161
+ self.assertEqual(config.category, "people_counting")
162
+ self.assertEqual(config.usecase, "people_counting")
163
+ self.assertEqual(config.confidence_threshold, 0.6)
164
+ self.assertTrue(config.enable_tracking)
165
+ self.assertTrue(config.enable_unique_counting)
166
+ self.assertEqual(config.time_window_minutes, 30)
167
+ self.assertEqual(config.person_categories, ["person", "people"])
168
+ self.assertEqual(config.zones, zones)
169
+
170
+ def test_people_counting_config_validation(self):
171
+ """Test people counting configuration validation."""
172
+ # Valid config
173
+ valid_config = PeopleCountingConfig(
174
+ confidence_threshold=0.6,
175
+ time_window_minutes=30,
176
+ person_categories=["person"]
177
+ )
178
+
179
+ errors = valid_config.validate()
180
+ self.assertEqual(len(errors), 0)
181
+
182
+ # Invalid time window
183
+ invalid_config = PeopleCountingConfig(
184
+ confidence_threshold=0.6,
185
+ time_window_minutes=-10, # Negative
186
+ person_categories=["person"]
187
+ )
188
+
189
+ errors = invalid_config.validate()
190
+ self.assertGreater(len(errors), 0)
191
+
192
+ # Empty person categories
193
+ invalid_config2 = PeopleCountingConfig(
194
+ confidence_threshold=0.6,
195
+ person_categories=[] # Empty
196
+ )
197
+
198
+ errors = invalid_config2.validate()
199
+ self.assertGreater(len(errors), 0)
200
+
201
+ def test_people_counting_config_with_zones(self):
202
+ """Test people counting configuration with zone validation."""
203
+ # Valid zones
204
+ valid_zones = create_zone_polygons(["entrance", "lobby"])
205
+
206
+ config = PeopleCountingConfig(
207
+ confidence_threshold=0.6,
208
+ zones=valid_zones
209
+ )
210
+
211
+ errors = config.validate()
212
+ self.assertEqual(len(errors), 0)
213
+
214
+ # Invalid zones (too few points)
215
+ invalid_zones = {
216
+ "entrance": [[0, 0], [100, 0]] # Only 2 points
217
+ }
218
+
219
+ config_invalid = PeopleCountingConfig(
220
+ confidence_threshold=0.6,
221
+ zones=invalid_zones
222
+ )
223
+
224
+ errors = config_invalid.validate()
225
+ self.assertGreater(len(errors), 0)
226
+
227
+ def test_people_counting_config_serialization(self):
228
+ """Test people counting configuration serialization."""
229
+ zones = create_zone_polygons(["entrance", "lobby"])
230
+
231
+ config = PeopleCountingConfig(
232
+ confidence_threshold=0.7,
233
+ enable_unique_counting=True,
234
+ zones=zones,
235
+ person_categories=["person", "people"]
236
+ )
237
+
238
+ # Serialize and deserialize
239
+ config_dict = config.to_dict()
240
+ restored_config = PeopleCountingConfig.from_dict(config_dict)
241
+
242
+ self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
243
+ self.assertEqual(restored_config.enable_unique_counting, config.enable_unique_counting)
244
+ self.assertEqual(restored_config.zones, config.zones)
245
+ self.assertEqual(restored_config.person_categories, config.person_categories)
246
+
247
+
248
+ class TestCustomerServiceConfig(BasePostProcessingTest):
249
+ """Test CustomerServiceConfig functionality."""
250
+
251
+ def test_customer_service_config_creation(self):
252
+ """Test customer service configuration creation."""
253
+ areas = create_customer_service_areas()
254
+
255
+ config = CustomerServiceConfig(
256
+ confidence_threshold=0.7,
257
+ enable_tracking=True,
258
+ customer_categories=["customer", "person"],
259
+ staff_categories=["staff", "employee"],
260
+ service_proximity_threshold=150.0,
261
+ max_service_time=600.0,
262
+ **areas
263
+ )
264
+
265
+ self.assertEqual(config.category, "customer_service")
266
+ self.assertEqual(config.usecase, "customer_service")
267
+ self.assertEqual(config.confidence_threshold, 0.7)
268
+ self.assertEqual(config.customer_categories, ["customer", "person"])
269
+ self.assertEqual(config.staff_categories, ["staff", "employee"])
270
+ self.assertEqual(config.service_proximity_threshold, 150.0)
271
+ self.assertEqual(config.max_service_time, 600.0)
272
+ self.assertIn("customer_areas", config.to_dict())
273
+ self.assertIn("staff_areas", config.to_dict())
274
+
275
+ def test_customer_service_config_validation(self):
276
+ """Test customer service configuration validation."""
277
+ areas = create_customer_service_areas()
278
+
279
+ # Valid config
280
+ valid_config = CustomerServiceConfig(
281
+ confidence_threshold=0.7,
282
+ customer_categories=["customer"],
283
+ staff_categories=["staff"],
284
+ service_proximity_threshold=150.0,
285
+ **areas
286
+ )
287
+
288
+ errors = valid_config.validate()
289
+ self.assertEqual(len(errors), 0)
290
+
291
+ # Invalid proximity threshold
292
+ invalid_config = CustomerServiceConfig(
293
+ confidence_threshold=0.7,
294
+ customer_categories=["customer"],
295
+ staff_categories=["staff"],
296
+ service_proximity_threshold=-50.0, # Negative
297
+ **areas
298
+ )
299
+
300
+ errors = invalid_config.validate()
301
+ self.assertGreater(len(errors), 0)
302
+
303
+ # Empty categories
304
+ invalid_config2 = CustomerServiceConfig(
305
+ confidence_threshold=0.7,
306
+ customer_categories=[], # Empty
307
+ staff_categories=["staff"],
308
+ **areas
309
+ )
310
+
311
+ errors = invalid_config2.validate()
312
+ self.assertGreater(len(errors), 0)
313
+
314
+ def test_customer_service_config_serialization(self):
315
+ """Test customer service configuration serialization."""
316
+ areas = create_customer_service_areas()
317
+
318
+ config = CustomerServiceConfig(
319
+ confidence_threshold=0.8,
320
+ customer_categories=["person"],
321
+ staff_categories=["staff"],
322
+ service_proximity_threshold=100.0,
323
+ **areas
324
+ )
325
+
326
+ # Test JSON serialization
327
+ json_str = config.to_json()
328
+ restored_config = CustomerServiceConfig.from_json(json_str)
329
+
330
+ self.assertEqual(restored_config.confidence_threshold, config.confidence_threshold)
331
+ self.assertEqual(restored_config.customer_categories, config.customer_categories)
332
+ self.assertEqual(restored_config.service_proximity_threshold, config.service_proximity_threshold)
333
+
334
+
335
+ class TestBasicCountingTrackingConfig(BasePostProcessingTest):
336
+ """Test BasicCountingTrackingConfig functionality."""
337
+
338
+ def test_basic_counting_tracking_config_creation(self):
339
+ """Test basic counting tracking configuration creation."""
340
+ zones = create_zone_polygons(["zone_a", "zone_b"])
341
+ lines = {
342
+ "entrance_line": [[100, 200], [200, 200]],
343
+ "exit_line": [[400, 200], [500, 200]]
344
+ }
345
+
346
+ config = BasicCountingTrackingConfig(
347
+ confidence_threshold=0.5,
348
+ enable_tracking=True,
349
+ zones=zones,
350
+ lines=lines
351
+ )
352
+
353
+ self.assertEqual(config.category, "general")
354
+ self.assertEqual(config.usecase, "basic_counting_tracking")
355
+ self.assertEqual(config.confidence_threshold, 0.5)
356
+ self.assertTrue(config.enable_tracking)
357
+ self.assertEqual(config.zones, zones)
358
+ self.assertEqual(config.lines, lines)
359
+
360
+ def test_basic_counting_tracking_config_validation(self):
361
+ """Test basic counting tracking configuration validation."""
362
+ # Valid config
363
+ valid_config = BasicCountingTrackingConfig(
364
+ confidence_threshold=0.6,
365
+ enable_tracking=True
366
+ )
367
+
368
+ errors = valid_config.validate()
369
+ self.assertEqual(len(errors), 0)
370
+
371
+ # Valid config with zones and lines
372
+ zones = create_zone_polygons(["zone_a"])
373
+ lines = {"line1": [[0, 100], [200, 100]]}
374
+
375
+ valid_config_full = BasicCountingTrackingConfig(
376
+ confidence_threshold=0.6,
377
+ zones=zones,
378
+ lines=lines
379
+ )
380
+
381
+ errors = valid_config_full.validate()
382
+ self.assertEqual(len(errors), 0)
383
+
384
+ # Invalid line (not enough points)
385
+ invalid_lines = {"line1": [[0, 100]]} # Only 1 point
386
+
387
+ invalid_config = BasicCountingTrackingConfig(
388
+ confidence_threshold=0.6,
389
+ lines=invalid_lines
390
+ )
391
+
392
+ errors = invalid_config.validate()
393
+ self.assertGreater(len(errors), 0)
394
+
395
+
396
+ class TestZoneConfig(BasePostProcessingTest):
397
+ """Test ZoneConfig functionality."""
398
+
399
+ def test_zone_config_creation(self):
400
+ """Test zone configuration creation."""
401
+ polygon = [[0, 0], [100, 0], [100, 100], [0, 100]]
402
+
403
+ zone_config = ZoneConfig(
404
+ name="test_zone",
405
+ polygon=polygon,
406
+ zone_type="entrance",
407
+ enabled=True
408
+ )
409
+
410
+ self.assertEqual(zone_config.name, "test_zone")
411
+ self.assertEqual(zone_config.polygon, polygon)
412
+ self.assertEqual(zone_config.zone_type, "entrance")
413
+ self.assertTrue(zone_config.enabled)
414
+
415
+ def test_zone_config_validation(self):
416
+ """Test zone configuration validation."""
417
+ # Valid zone
418
+ valid_polygon = [[0, 0], [100, 0], [100, 100], [0, 100]]
419
+ valid_zone = ZoneConfig(
420
+ name="valid_zone",
421
+ polygon=valid_polygon
422
+ )
423
+
424
+ errors = valid_zone.validate()
425
+ self.assertEqual(len(errors), 0)
426
+
427
+ # Invalid zone (too few points)
428
+ invalid_polygon = [[0, 0], [100, 0]]
429
+ invalid_zone = ZoneConfig(
430
+ name="invalid_zone",
431
+ polygon=invalid_polygon
432
+ )
433
+
434
+ errors = invalid_zone.validate()
435
+ self.assertGreater(len(errors), 0)
436
+
437
+ # Empty name
438
+ invalid_zone2 = ZoneConfig(
439
+ name="",
440
+ polygon=valid_polygon
441
+ )
442
+
443
+ errors = invalid_zone2.validate()
444
+ self.assertGreater(len(errors), 0)
445
+
446
+
447
+ class TestAlertConfig(BasePostProcessingTest):
448
+ """Test AlertConfig functionality."""
449
+
450
+ def test_alert_config_creation(self):
451
+ """Test alert configuration creation."""
452
+ alert_config = AlertConfig(
453
+ enabled=True,
454
+ count_thresholds={"person": 50, "vehicle": 20},
455
+ occupancy_thresholds={"lobby": 30, "entrance": 15},
456
+ alert_cooldown_minutes=5
457
+ )
458
+
459
+ self.assertTrue(alert_config.enabled)
460
+ self.assertEqual(alert_config.count_thresholds["person"], 50)
461
+ self.assertEqual(alert_config.occupancy_thresholds["lobby"], 30)
462
+ self.assertEqual(alert_config.alert_cooldown_minutes, 5)
463
+
464
+ def test_alert_config_validation(self):
465
+ """Test alert configuration validation."""
466
+ # Valid config
467
+ valid_config = AlertConfig(
468
+ enabled=True,
469
+ count_thresholds={"person": 50},
470
+ alert_cooldown_minutes=5
471
+ )
472
+
473
+ errors = valid_config.validate()
474
+ self.assertEqual(len(errors), 0)
475
+
476
+ # Invalid cooldown
477
+ invalid_config = AlertConfig(
478
+ enabled=True,
479
+ alert_cooldown_minutes=-1 # Negative
480
+ )
481
+
482
+ errors = invalid_config.validate()
483
+ self.assertGreater(len(errors), 0)
484
+
485
+ # Invalid threshold values
486
+ invalid_config2 = AlertConfig(
487
+ enabled=True,
488
+ count_thresholds={"person": -10} # Negative threshold
489
+ )
490
+
491
+ errors = invalid_config2.validate()
492
+ self.assertGreater(len(errors), 0)
493
+
494
+
495
+ class TestTrackingConfig(BasePostProcessingTest):
496
+ """Test TrackingConfig functionality."""
497
+
498
+ def test_tracking_config_creation(self):
499
+ """Test tracking configuration creation."""
500
+ tracking_config = TrackingConfig(
501
+ enabled=True,
502
+ max_age=30,
503
+ min_hits=3,
504
+ iou_threshold=0.3,
505
+ track_buffer_size=1000
506
+ )
507
+
508
+ self.assertTrue(tracking_config.enabled)
509
+ self.assertEqual(tracking_config.max_age, 30)
510
+ self.assertEqual(tracking_config.min_hits, 3)
511
+ self.assertEqual(tracking_config.iou_threshold, 0.3)
512
+ self.assertEqual(tracking_config.track_buffer_size, 1000)
513
+
514
+ def test_tracking_config_validation(self):
515
+ """Test tracking configuration validation."""
516
+ # Valid config
517
+ valid_config = TrackingConfig(
518
+ enabled=True,
519
+ max_age=30,
520
+ min_hits=3,
521
+ iou_threshold=0.3
522
+ )
523
+
524
+ errors = valid_config.validate()
525
+ self.assertEqual(len(errors), 0)
526
+
527
+ # Invalid IoU threshold
528
+ invalid_config = TrackingConfig(
529
+ enabled=True,
530
+ iou_threshold=1.5 # > 1.0
531
+ )
532
+
533
+ errors = invalid_config.validate()
534
+ self.assertGreater(len(errors), 0)
535
+
536
+ # Invalid max_age
537
+ invalid_config2 = TrackingConfig(
538
+ enabled=True,
539
+ max_age=0 # Should be > 0
540
+ )
541
+
542
+ errors = invalid_config2.validate()
543
+ self.assertGreater(len(errors), 0)
544
+
545
+
546
+ class TestConfigManager(BasePostProcessingTest):
547
+ """Test ConfigManager functionality."""
548
+
549
+ def setUp(self):
550
+ """Set up test environment."""
551
+ super().setUp()
552
+ self.config_manager = ConfigManager()
553
+
554
+ def test_config_registration(self):
555
+ """Test configuration registration."""
556
+ # Register configs
557
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
558
+ self.config_manager.register_config("customer_service", CustomerServiceConfig)
559
+
560
+ # Check registration
561
+ self.assertIn("people_counting", self.config_manager.get_registered_configs())
562
+ self.assertIn("customer_service", self.config_manager.get_registered_configs())
563
+
564
+ def test_config_creation(self):
565
+ """Test configuration creation through manager."""
566
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
567
+
568
+ # Create config
569
+ config = self.config_manager.create_config(
570
+ "people_counting",
571
+ confidence_threshold=0.7,
572
+ enable_tracking=True
573
+ )
574
+
575
+ self.assertIsInstance(config, PeopleCountingConfig)
576
+ self.assertEqual(config.confidence_threshold, 0.7)
577
+ self.assertTrue(config.enable_tracking)
578
+
579
+ def test_config_validation_through_manager(self):
580
+ """Test configuration validation through manager."""
581
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
582
+
583
+ # Valid config
584
+ valid_config = self.config_manager.create_config(
585
+ "people_counting",
586
+ confidence_threshold=0.6
587
+ )
588
+
589
+ is_valid, errors = self.config_manager.validate_config(valid_config)
590
+ self.assertTrue(is_valid)
591
+ self.assertEqual(len(errors), 0)
592
+
593
+ # Invalid config
594
+ invalid_config = PeopleCountingConfig(
595
+ confidence_threshold=1.5 # Invalid
596
+ )
597
+
598
+ is_valid, errors = self.config_manager.validate_config(invalid_config)
599
+ self.assertFalse(is_valid)
600
+ self.assertGreater(len(errors), 0)
601
+
602
+ def test_config_file_operations(self):
603
+ """Test configuration file save/load operations."""
604
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
605
+
606
+ # Create config
607
+ config = self.config_manager.create_config(
608
+ "people_counting",
609
+ confidence_threshold=0.8,
610
+ enable_tracking=True,
611
+ person_categories=["person", "people"]
612
+ )
613
+
614
+ # Save to file
615
+ config_file = os.path.join(self.temp_dir, "test_config.json")
616
+ self.config_manager.save_config_to_file(config, config_file)
617
+
618
+ # Check file exists
619
+ self.assertTrue(os.path.exists(config_file))
620
+
621
+ # Load from file
622
+ loaded_config = self.config_manager.load_config_from_file(config_file)
623
+
624
+ self.assertIsInstance(loaded_config, PeopleCountingConfig)
625
+ self.assertEqual(loaded_config.confidence_threshold, config.confidence_threshold)
626
+ self.assertEqual(loaded_config.enable_tracking, config.enable_tracking)
627
+ self.assertEqual(loaded_config.person_categories, config.person_categories)
628
+
629
+ def test_config_yaml_operations(self):
630
+ """Test YAML configuration operations."""
631
+ self.config_manager.register_config("customer_service", CustomerServiceConfig)
632
+
633
+ areas = create_customer_service_areas()
634
+ config = self.config_manager.create_config(
635
+ "customer_service",
636
+ confidence_threshold=0.7,
637
+ customer_categories=["person"],
638
+ staff_categories=["staff"],
639
+ **areas
640
+ )
641
+
642
+ # Save to YAML file
643
+ yaml_file = os.path.join(self.temp_dir, "test_config.yaml")
644
+ self.config_manager.save_config_to_file(config, yaml_file, format="yaml")
645
+
646
+ # Load from YAML file
647
+ loaded_config = self.config_manager.load_config_from_file(yaml_file)
648
+
649
+ self.assertIsInstance(loaded_config, CustomerServiceConfig)
650
+ self.assertEqual(loaded_config.confidence_threshold, config.confidence_threshold)
651
+ self.assertEqual(loaded_config.customer_categories, config.customer_categories)
652
+
653
+ def test_config_import_export(self):
654
+ """Test configuration import/export functionality."""
655
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
656
+
657
+ # Create multiple configs
658
+ configs = {
659
+ "config1": self.config_manager.create_config(
660
+ "people_counting",
661
+ confidence_threshold=0.6
662
+ ),
663
+ "config2": self.config_manager.create_config(
664
+ "people_counting",
665
+ confidence_threshold=0.8,
666
+ enable_tracking=True
667
+ )
668
+ }
669
+
670
+ # Export configs
671
+ export_file = os.path.join(self.temp_dir, "exported_configs.json")
672
+ self.config_manager.export_configs(configs, export_file)
673
+
674
+ # Import configs
675
+ imported_configs = self.config_manager.import_configs(export_file)
676
+
677
+ self.assertEqual(len(imported_configs), 2)
678
+ self.assertIn("config1", imported_configs)
679
+ self.assertIn("config2", imported_configs)
680
+
681
+ # Check imported config values
682
+ self.assertEqual(imported_configs["config1"].confidence_threshold, 0.6)
683
+ self.assertEqual(imported_configs["config2"].confidence_threshold, 0.8)
684
+ self.assertTrue(imported_configs["config2"].enable_tracking)
685
+
686
+ def test_config_schema_generation(self):
687
+ """Test configuration schema generation."""
688
+ self.config_manager.register_config("people_counting", PeopleCountingConfig)
689
+
690
+ # Get schema
691
+ schema = self.config_manager.get_config_schema("people_counting")
692
+
693
+ self.assertIsInstance(schema, dict)
694
+ self.assertIn("type", schema)
695
+ self.assertIn("properties", schema)
696
+
697
+ # Check for expected properties
698
+ properties = schema["properties"]
699
+ self.assertIn("confidence_threshold", properties)
700
+ self.assertIn("enable_tracking", properties)
701
+ self.assertIn("person_categories", properties)
702
+
703
+ def test_config_defaults(self):
704
+ """Test configuration default values."""
705
+ self.config_manager.register_config("basic_counting_tracking", BasicCountingTrackingConfig)
706
+
707
+ # Create config with defaults
708
+ config = self.config_manager.create_config("basic_counting_tracking")
709
+
710
+ # Should have default values
711
+ self.assertIsNotNone(config.confidence_threshold)
712
+ self.assertIsNotNone(config.category)
713
+ self.assertIsNotNone(config.usecase)
714
+
715
+ # Validate defaults
716
+ is_valid, errors = self.config_manager.validate_config(config)
717
+ self.assertTrue(is_valid, f"Default config should be valid: {errors}")
718
+
719
+
720
+ class TestConfigIntegration(BasePostProcessingTest):
721
+ """Integration tests for configuration system."""
722
+
723
+ def test_config_roundtrip_serialization(self):
724
+ """Test complete serialization roundtrip."""
725
+ areas = create_customer_service_areas()
726
+ zones = create_zone_polygons(["entrance", "lobby"])
727
+
728
+ original_config = CustomerServiceConfig(
729
+ confidence_threshold=0.75,
730
+ enable_tracking=True,
731
+ customer_categories=["person", "customer"],
732
+ staff_categories=["staff", "employee"],
733
+ service_proximity_threshold=120.0,
734
+ max_service_time=480.0,
735
+ zones=zones,
736
+ **areas
737
+ )
738
+
739
+ # JSON roundtrip
740
+ json_str = original_config.to_json()
741
+ json_restored = CustomerServiceConfig.from_json(json_str)
742
+
743
+ self.assertEqual(json_restored.confidence_threshold, original_config.confidence_threshold)
744
+ self.assertEqual(json_restored.customer_categories, original_config.customer_categories)
745
+ self.assertEqual(json_restored.service_proximity_threshold, original_config.service_proximity_threshold)
746
+
747
+ # Dict roundtrip
748
+ config_dict = original_config.to_dict()
749
+ dict_restored = CustomerServiceConfig.from_dict(config_dict)
750
+
751
+ self.assertEqual(dict_restored.confidence_threshold, original_config.confidence_threshold)
752
+ self.assertEqual(dict_restored.zones, original_config.zones)
753
+
754
+ def test_config_parameter_override(self):
755
+ """Test configuration parameter override behavior."""
756
+ base_config = PeopleCountingConfig(
757
+ confidence_threshold=0.5,
758
+ enable_tracking=False,
759
+ time_window_minutes=15
760
+ )
761
+
762
+ # Override with new parameters
763
+ override_dict = {
764
+ "confidence_threshold": 0.8,
765
+ "enable_tracking": True,
766
+ "enable_unique_counting": True # New parameter
767
+ }
768
+
769
+ # Create new config with overrides
770
+ overridden_config = PeopleCountingConfig(
771
+ **{**base_config.to_dict(), **override_dict}
772
+ )
773
+
774
+ # Check overrides
775
+ self.assertEqual(overridden_config.confidence_threshold, 0.8)
776
+ self.assertTrue(overridden_config.enable_tracking)
777
+ self.assertTrue(overridden_config.enable_unique_counting)
778
+
779
+ # Check preserved values
780
+ self.assertEqual(overridden_config.time_window_minutes, 15)
781
+
782
+ def test_config_validation_edge_cases(self):
783
+ """Test configuration validation with edge cases."""
784
+ # Test with None values
785
+ config_with_none = PeopleCountingConfig(
786
+ confidence_threshold=None, # Should use default
787
+ person_categories=None # Should use default
788
+ )
789
+
790
+ # Should handle None values gracefully
791
+ errors = config_with_none.validate()
792
+ # May have errors or use defaults - depends on implementation
793
+
794
+ # Test with extreme values
795
+ extreme_config = PeopleCountingConfig(
796
+ confidence_threshold=0.0001, # Very low but valid
797
+ time_window_minutes=10080 # 1 week in minutes
798
+ )
799
+
800
+ errors = extreme_config.validate()
801
+ self.assertEqual(len(errors), 0) # Should be valid
802
+
803
+ def test_multiple_config_types_integration(self):
804
+ """Test integration of multiple configuration types."""
805
+ manager = ConfigManager()
806
+
807
+ # Register all config types
808
+ manager.register_config("people_counting", PeopleCountingConfig)
809
+ manager.register_config("customer_service", CustomerServiceConfig)
810
+ manager.register_config("basic_counting_tracking", BasicCountingTrackingConfig)
811
+
812
+ # Create configs of different types
813
+ configs = {
814
+ "people_config": manager.create_config(
815
+ "people_counting",
816
+ confidence_threshold=0.6,
817
+ enable_tracking=True
818
+ ),
819
+ "service_config": manager.create_config(
820
+ "customer_service",
821
+ confidence_threshold=0.7,
822
+ customer_categories=["person"],
823
+ staff_categories=["staff"]
824
+ ),
825
+ "basic_config": manager.create_config(
826
+ "basic_counting_tracking",
827
+ confidence_threshold=0.5,
828
+ enable_tracking=True
829
+ )
830
+ }
831
+
832
+ # Validate all configs
833
+ for name, config in configs.items():
834
+ is_valid, errors = manager.validate_config(config)
835
+ self.assertTrue(is_valid, f"Config {name} should be valid: {errors}")
836
+
837
+ # Export all configs
838
+ export_file = os.path.join(self.temp_dir, "multi_configs.json")
839
+ manager.export_configs(configs, export_file)
840
+
841
+ # Import and verify
842
+ imported = manager.import_configs(export_file)
843
+ self.assertEqual(len(imported), 3)
844
+
845
+ # Check types are preserved
846
+ self.assertIsInstance(imported["people_config"], PeopleCountingConfig)
847
+ self.assertIsInstance(imported["service_config"], CustomerServiceConfig)
848
+ self.assertIsInstance(imported["basic_config"], BasicCountingTrackingConfig)
849
+
850
+
851
+ if __name__ == "__main__":
852
+ unittest.main()