chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)

The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).

Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.

Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.

Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
  (numpy/scipy still required; CI installs requirements-lock.txt
  from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
  0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)

After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
This commit is contained in:
rUv
2026-04-25 23:07:52 -04:00
committed by GitHub
parent 74233cfb23
commit 81cc241b9e
183 changed files with 290 additions and 216 deletions
+21
View File
@@ -0,0 +1,21 @@
"""
Testing utilities for WiFi-DensePose.
This module contains mock data generators and testing helpers that are
ONLY intended for use in development/testing environments. These generators
produce synthetic data that mimics real CSI and pose data patterns.
WARNING: Code in this module uses random number generation intentionally
for mock/test data. Do NOT import from this module in production code paths
unless behind an explicit mock_mode flag with appropriate logging.
"""
from .mock_csi_generator import MockCSIGenerator
from .mock_pose_generator import generate_mock_poses, generate_mock_keypoints, generate_mock_bounding_box
__all__ = [
"MockCSIGenerator",
"generate_mock_poses",
"generate_mock_keypoints",
"generate_mock_bounding_box",
]
@@ -0,0 +1,178 @@
"""
Mock CSI data generator for testing and development.
This module provides synthetic CSI (Channel State Information) data generation
for use in development and testing environments ONLY. The generated data mimics
realistic WiFi CSI patterns including multipath effects, human motion signatures,
and noise characteristics.
WARNING: This module uses np.random intentionally for test data generation.
Do NOT use this module in production data paths.
"""
import logging
import numpy as np
from typing import Dict, Any, Optional
logger = logging.getLogger(__name__)
# Banner displayed when mock mode is active
MOCK_MODE_BANNER = """
================================================================================
WARNING: MOCK MODE ACTIVE - Using synthetic CSI data
All CSI data is randomly generated and does NOT represent real WiFi signals.
For real pose estimation, configure hardware per docs/hardware-setup.md.
================================================================================
"""
class MockCSIGenerator:
"""Generator for synthetic CSI data used in testing and development.
This class produces complex-valued CSI matrices that simulate realistic
WiFi channel characteristics including:
- Per-antenna and per-subcarrier amplitude/phase variation
- Simulated human movement signatures
- Configurable noise levels
- Temporal coherence across consecutive frames
This is ONLY for testing. Production code must use real hardware data.
"""
def __init__(
self,
num_subcarriers: int = 64,
num_antennas: int = 4,
num_samples: int = 100,
noise_level: float = 0.1,
movement_freq: float = 0.5,
movement_amplitude: float = 0.3,
):
"""Initialize mock CSI generator.
Args:
num_subcarriers: Number of OFDM subcarriers to simulate
num_antennas: Number of antenna elements
num_samples: Number of temporal samples per frame
noise_level: Standard deviation of additive Gaussian noise
movement_freq: Frequency of simulated human movement (Hz)
movement_amplitude: Amplitude of movement-induced CSI variation
"""
self.num_subcarriers = num_subcarriers
self.num_antennas = num_antennas
self.num_samples = num_samples
self.noise_level = noise_level
self.movement_freq = movement_freq
self.movement_amplitude = movement_amplitude
# Internal state for temporal coherence
self._phase = 0.0
self._frequency = 0.1
self._amplitude_base = 1.0
self._banner_shown = False
def show_banner(self) -> None:
"""Display the mock mode warning banner (once per session)."""
if not self._banner_shown:
logger.warning(MOCK_MODE_BANNER)
self._banner_shown = True
def generate(self) -> np.ndarray:
"""Generate a single frame of mock CSI data.
Returns:
Complex-valued numpy array of shape
(num_antennas, num_subcarriers, num_samples).
"""
self.show_banner()
# Advance internal phase for temporal coherence
self._phase += self._frequency
time_axis = np.linspace(0, 1, self.num_samples)
csi_data = np.zeros(
(self.num_antennas, self.num_subcarriers, self.num_samples),
dtype=complex,
)
for antenna in range(self.num_antennas):
for subcarrier in range(self.num_subcarriers):
# Base amplitude varies with antenna and subcarrier
amplitude = (
self._amplitude_base
* (1 + 0.2 * np.sin(2 * np.pi * subcarrier / self.num_subcarriers))
* (1 + 0.1 * antenna)
)
# Phase with spatial and frequency variation
phase_offset = (
self._phase
+ 2 * np.pi * subcarrier / self.num_subcarriers
+ np.pi * antenna / self.num_antennas
)
# Simulated human movement
movement = self.movement_amplitude * np.sin(
2 * np.pi * self.movement_freq * time_axis
)
signal_amplitude = amplitude * (1 + movement)
signal_phase = phase_offset + movement * 0.5
# Additive complex Gaussian noise
noise = np.random.normal(0, self.noise_level, self.num_samples) + 1j * np.random.normal(
0, self.noise_level, self.num_samples
)
csi_data[antenna, subcarrier, :] = (
signal_amplitude * np.exp(1j * signal_phase) + noise
)
return csi_data
def configure(self, config: Dict[str, Any]) -> None:
"""Update generator parameters.
Args:
config: Dictionary with optional keys:
- sampling_rate: Adjusts internal frequency
- noise_level: Sets noise standard deviation
- num_subcarriers: Updates subcarrier count
- num_antennas: Updates antenna count
- movement_freq: Updates simulated movement frequency
- movement_amplitude: Updates movement amplitude
"""
if "sampling_rate" in config:
self._frequency = config["sampling_rate"] / 1000.0
if "noise_level" in config:
self.noise_level = config["noise_level"]
if "num_subcarriers" in config:
self.num_subcarriers = config["num_subcarriers"]
if "num_antennas" in config:
self.num_antennas = config["num_antennas"]
if "movement_freq" in config:
self.movement_freq = config["movement_freq"]
if "movement_amplitude" in config:
self.movement_amplitude = config["movement_amplitude"]
def get_router_info(self) -> Dict[str, Any]:
"""Return mock router hardware information.
Returns:
Dictionary mimicking router hardware info for testing.
"""
return {
"model": "Mock Router",
"firmware": "1.0.0-mock",
"wifi_standard": "802.11ac",
"antennas": self.num_antennas,
"supported_bands": ["2.4GHz", "5GHz"],
"csi_capabilities": {
"max_subcarriers": self.num_subcarriers,
"max_antennas": self.num_antennas,
"sampling_rate": 1000,
},
}
@@ -0,0 +1,301 @@
"""
Mock pose data generator for testing and development.
This module provides synthetic pose estimation data for use in development
and testing environments ONLY. The generated data mimics realistic human
pose detection outputs including keypoints, bounding boxes, and activities.
WARNING: This module uses random number generation intentionally for test data.
Do NOT use this module in production data paths.
"""
import random
import logging
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
# Banner displayed when mock pose mode is active
MOCK_POSE_BANNER = """
================================================================================
WARNING: MOCK POSE MODE ACTIVE - Using synthetic pose data
All pose detections are randomly generated and do NOT represent real humans.
For real pose estimation, provide trained model weights and real CSI data.
See docs/hardware-setup.md for configuration instructions.
================================================================================
"""
_banner_shown = False
def _show_banner() -> None:
"""Display the mock pose mode warning banner (once per session)."""
global _banner_shown
if not _banner_shown:
logger.warning(MOCK_POSE_BANNER)
_banner_shown = True
def generate_mock_keypoints() -> List[Dict[str, Any]]:
"""Generate mock keypoints for a single person.
Returns:
List of 17 COCO-format keypoint dictionaries with name, x, y, confidence.
"""
keypoint_names = [
"nose", "left_eye", "right_eye", "left_ear", "right_ear",
"left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
"left_wrist", "right_wrist", "left_hip", "right_hip",
"left_knee", "right_knee", "left_ankle", "right_ankle",
]
keypoints = []
for name in keypoint_names:
keypoints.append({
"name": name,
"x": random.uniform(0.1, 0.9),
"y": random.uniform(0.1, 0.9),
"confidence": random.uniform(0.5, 0.95),
})
return keypoints
def generate_mock_bounding_box() -> Dict[str, float]:
"""Generate a mock bounding box for a single person.
Returns:
Dictionary with x, y, width, height as normalized coordinates.
"""
x = random.uniform(0.1, 0.6)
y = random.uniform(0.1, 0.6)
width = random.uniform(0.2, 0.4)
height = random.uniform(0.3, 0.5)
return {"x": x, "y": y, "width": width, "height": height}
def generate_mock_poses(max_persons: int = 3) -> List[Dict[str, Any]]:
"""Generate mock pose detections for testing.
Args:
max_persons: Maximum number of persons to generate (1 to max_persons).
Returns:
List of pose detection dictionaries.
"""
_show_banner()
num_persons = random.randint(1, min(3, max_persons))
poses = []
for i in range(num_persons):
confidence = random.uniform(0.3, 0.95)
pose = {
"person_id": i,
"confidence": confidence,
"keypoints": generate_mock_keypoints(),
"bounding_box": generate_mock_bounding_box(),
"activity": random.choice(["standing", "sitting", "walking", "lying"]),
"timestamp": datetime.now().isoformat(),
}
poses.append(pose)
return poses
def generate_mock_zone_occupancy(zone_id: str) -> Dict[str, Any]:
"""Generate mock zone occupancy data.
Args:
zone_id: Zone identifier.
Returns:
Dictionary with occupancy count and person details.
"""
_show_banner()
count = random.randint(0, 5)
persons = []
for i in range(count):
persons.append({
"person_id": f"person_{i}",
"confidence": random.uniform(0.7, 0.95),
"activity": random.choice(["standing", "sitting", "walking"]),
})
return {
"count": count,
"max_occupancy": 10,
"persons": persons,
"timestamp": datetime.now(),
}
def generate_mock_zones_summary(
zone_ids: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""Generate mock zones summary data.
Args:
zone_ids: List of zone identifiers. Defaults to zone_1 through zone_4.
Returns:
Dictionary with per-zone occupancy and aggregate counts.
"""
_show_banner()
zones = zone_ids or ["zone_1", "zone_2", "zone_3", "zone_4"]
zone_data = {}
total_persons = 0
active_zones = 0
for zone_id in zones:
count = random.randint(0, 3)
zone_data[zone_id] = {
"occupancy": count,
"max_occupancy": 10,
"status": "active" if count > 0 else "inactive",
}
total_persons += count
if count > 0:
active_zones += 1
return {
"total_persons": total_persons,
"zones": zone_data,
"active_zones": active_zones,
}
def generate_mock_historical_data(
start_time: datetime,
end_time: datetime,
zone_ids: Optional[List[str]] = None,
aggregation_interval: int = 300,
include_raw_data: bool = False,
) -> Dict[str, Any]:
"""Generate mock historical pose data.
Args:
start_time: Start of the time range.
end_time: End of the time range.
zone_ids: Zones to include. Defaults to zone_1, zone_2, zone_3.
aggregation_interval: Seconds between data points.
include_raw_data: Whether to include simulated raw detections.
Returns:
Dictionary with aggregated_data, optional raw_data, and total_records.
"""
_show_banner()
zones = zone_ids or ["zone_1", "zone_2", "zone_3"]
current_time = start_time
aggregated_data = []
raw_data = [] if include_raw_data else None
while current_time < end_time:
data_point = {
"timestamp": current_time,
"total_persons": random.randint(0, 8),
"zones": {},
}
for zone_id in zones:
data_point["zones"][zone_id] = {
"occupancy": random.randint(0, 3),
"avg_confidence": random.uniform(0.7, 0.95),
}
aggregated_data.append(data_point)
if include_raw_data:
for _ in range(random.randint(0, 5)):
raw_data.append({
"timestamp": current_time + timedelta(seconds=random.randint(0, aggregation_interval)),
"person_id": f"person_{random.randint(1, 10)}",
"zone_id": random.choice(zones),
"confidence": random.uniform(0.5, 0.95),
"activity": random.choice(["standing", "sitting", "walking"]),
})
current_time += timedelta(seconds=aggregation_interval)
return {
"aggregated_data": aggregated_data,
"raw_data": raw_data,
"total_records": len(aggregated_data),
}
def generate_mock_recent_activities(
zone_id: Optional[str] = None,
limit: int = 10,
) -> List[Dict[str, Any]]:
"""Generate mock recent activity data.
Args:
zone_id: Optional zone filter. If None, random zones are used.
limit: Number of activities to generate.
Returns:
List of activity dictionaries.
"""
_show_banner()
activities = []
for i in range(limit):
activity = {
"activity_id": f"activity_{i}",
"person_id": f"person_{random.randint(1, 5)}",
"zone_id": zone_id or random.choice(["zone_1", "zone_2", "zone_3"]),
"activity": random.choice(["standing", "sitting", "walking", "lying"]),
"confidence": random.uniform(0.6, 0.95),
"timestamp": datetime.now() - timedelta(minutes=random.randint(0, 60)),
"duration_seconds": random.randint(10, 300),
}
activities.append(activity)
return activities
def generate_mock_statistics(
start_time: datetime,
end_time: datetime,
) -> Dict[str, Any]:
"""Generate mock pose estimation statistics.
Args:
start_time: Start of the statistics period.
end_time: End of the statistics period.
Returns:
Dictionary with detection counts, rates, and distributions.
"""
_show_banner()
total_detections = random.randint(100, 1000)
successful_detections = int(total_detections * random.uniform(0.8, 0.95))
return {
"total_detections": total_detections,
"successful_detections": successful_detections,
"failed_detections": total_detections - successful_detections,
"success_rate": successful_detections / total_detections,
"average_confidence": random.uniform(0.75, 0.90),
"average_processing_time_ms": random.uniform(50, 200),
"unique_persons": random.randint(5, 20),
"most_active_zone": random.choice(["zone_1", "zone_2", "zone_3"]),
"activity_distribution": {
"standing": random.uniform(0.3, 0.5),
"sitting": random.uniform(0.2, 0.4),
"walking": random.uniform(0.1, 0.3),
"lying": random.uniform(0.0, 0.1),
},
}