@mytec: 8.1 iter start
This commit is contained in:
267
RFCP-Iteration8.1-Clone-Coverage-Gaps.md
Normal file
267
RFCP-Iteration8.1-Clone-Coverage-Gaps.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# RFCP - Iteration 8.1: Clone Fix + Coverage Gaps
|
||||
|
||||
## Issue 1: Clone Creates New Site (Critical!)
|
||||
|
||||
**Problem:** Clone button creates new site instead of adding sector to existing site.
|
||||
|
||||
**Root Cause:** `cloneSector` function in sites store creates new site object.
|
||||
|
||||
### Solution
|
||||
|
||||
**File:** `frontend/src/store/sites.ts`
|
||||
|
||||
**Current (WRONG):**
|
||||
```typescript
|
||||
const cloneSector = (siteId: string) => {
|
||||
const site = sites.find(s => s.id === siteId);
|
||||
const clone = {
|
||||
...site,
|
||||
id: uuid(),
|
||||
name: `${site.name}-clone`
|
||||
};
|
||||
setSites([...sites, clone]); // Creates NEW site ❌
|
||||
};
|
||||
```
|
||||
|
||||
**Fixed (CORRECT):**
|
||||
```typescript
|
||||
const cloneSector = (siteId: string) => {
|
||||
const site = sites.find(s => s.id === siteId);
|
||||
if (!site) return;
|
||||
|
||||
// Get last sector as template
|
||||
const lastSector = site.sectors[site.sectors.length - 1];
|
||||
|
||||
// Create new sector (NOT new site!)
|
||||
const newSector: Sector = {
|
||||
...lastSector,
|
||||
id: `sector-${Date.now()}`,
|
||||
azimuth: (lastSector.azimuth + 120) % 360, // 120° offset for tri-sector
|
||||
};
|
||||
|
||||
// Add sector to EXISTING site ✅
|
||||
updateSite(siteId, {
|
||||
sectors: [...site.sectors, newSector]
|
||||
});
|
||||
|
||||
// Clear coverage to force recalculation
|
||||
useCoverageStore.getState().clearCoverage();
|
||||
};
|
||||
```
|
||||
|
||||
### Update Button Label
|
||||
|
||||
**File:** `frontend/src/components/panels/SiteList.tsx`
|
||||
|
||||
```typescript
|
||||
<button onClick={() => cloneSector(site.id)}>
|
||||
+ Add Sector {/* was: "Clone" */}
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue 2: Coverage Gaps at 800m Wide
|
||||
|
||||
**Problem:** At 800m heatmap radius with 300m resolution, coverage points don't overlap enough → visible dots.
|
||||
|
||||
**Why it happens:**
|
||||
- Resolution: 300m (distance between coverage points)
|
||||
- Heatmap radius: 800m
|
||||
- At high zoom, 800m radius in pixels is HUGE
|
||||
- But point spacing (300m) stays same
|
||||
- Result: Gaps between points visible
|
||||
|
||||
### Solution A: Warn User (Quick)
|
||||
|
||||
**File:** `frontend/src/components/panels/CoverageSettings.tsx`
|
||||
|
||||
```typescript
|
||||
<select
|
||||
value={heatmapRadius}
|
||||
onChange={(e) => setHeatmapRadius(Number(e.target.value))}
|
||||
>
|
||||
<option value={200}>200m — Fast</option>
|
||||
<option value={400}>400m — Balanced</option>
|
||||
<option value={600}>600m — Smooth</option>
|
||||
<option value={800}>800m — Wide</option>
|
||||
</select>
|
||||
|
||||
{/* Warning for 800m */}
|
||||
{heatmapRadius === 800 && resolution > 200 && (
|
||||
<div className="warning">
|
||||
⚠️ Wide radius works best with fine resolution (≤200m).
|
||||
Current: {resolution}m. Consider reducing for smoother coverage.
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
### Solution B: Auto-adjust Resolution (Better)
|
||||
|
||||
**File:** `frontend/src/store/coverage.ts`
|
||||
|
||||
```typescript
|
||||
const calculateCoverage = async () => {
|
||||
// Auto-adjust resolution based on heatmap radius
|
||||
// Rule: resolution should be ≤ radius/2 for smooth coverage
|
||||
const recommendedResolution = Math.min(
|
||||
resolution,
|
||||
Math.floor(heatmapRadius / 2)
|
||||
);
|
||||
|
||||
if (recommendedResolution < resolution) {
|
||||
console.log(`Auto-adjusting resolution: ${resolution}m → ${recommendedResolution}m for ${heatmapRadius}m radius`);
|
||||
}
|
||||
|
||||
const effectiveResolution = recommendedResolution;
|
||||
|
||||
// Calculate with adjusted resolution
|
||||
await worker.calculateCoverage({
|
||||
sites,
|
||||
radius,
|
||||
resolution: effectiveResolution,
|
||||
rsrpThreshold
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Solution C: Dynamic Point Sampling (Advanced)
|
||||
|
||||
**File:** `frontend/src/components/map/HeatmapTileRenderer.ts`
|
||||
|
||||
Add adaptive point sampling in renderer:
|
||||
|
||||
```typescript
|
||||
private drawPoint(
|
||||
intensityMap: Float32Array,
|
||||
point: CoveragePoint,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radiusPixels: number
|
||||
): void {
|
||||
// ... existing code
|
||||
|
||||
// ADAPTIVE: If radius is very large, increase sampling
|
||||
const sampleFactor = radiusPixels > 100 ? 2 : 1;
|
||||
|
||||
for (let y = minY; y < maxY; y += sampleFactor) {
|
||||
for (let x = minX; x < maxX; x += sampleFactor) {
|
||||
// ... draw with interpolation
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution D: Clamp Max Radius (Safest)
|
||||
|
||||
**File:** `frontend/src/components/panels/CoverageSettings.tsx`
|
||||
|
||||
```typescript
|
||||
// Limit radius based on resolution
|
||||
const maxAllowedRadius = resolution * 3; // 3x resolution max
|
||||
|
||||
<select
|
||||
value={heatmapRadius}
|
||||
onChange={(e) => {
|
||||
const newRadius = Number(e.target.value);
|
||||
if (newRadius > maxAllowedRadius) {
|
||||
toast.warning(`Radius ${newRadius}m too large for ${resolution}m resolution. Max: ${maxAllowedRadius}m`);
|
||||
return;
|
||||
}
|
||||
setHeatmapRadius(newRadius);
|
||||
}}
|
||||
>
|
||||
<option value={200} disabled={resolution > 100}>200m — Fast</option>
|
||||
<option value={400} disabled={resolution > 200}>400m — Balanced</option>
|
||||
<option value={600} disabled={resolution > 300}>600m — Smooth</option>
|
||||
<option value={800} disabled={resolution > 400}>800m — Wide</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue 3: Coverage Not Cleared on Sector Delete
|
||||
|
||||
**File:** `frontend/src/store/sites.ts`
|
||||
|
||||
```typescript
|
||||
const removeSector = (siteId: string, sectorId: string) => {
|
||||
const site = sites.find(s => s.id === siteId);
|
||||
if (!site || site.sectors.length <= 1) {
|
||||
toast.error('Cannot remove last sector');
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSectors = site.sectors.filter(s => s.id !== sectorId);
|
||||
updateSite(siteId, { sectors: updatedSectors });
|
||||
|
||||
// CRITICAL: Clear coverage!
|
||||
useCoverageStore.getState().clearCoverage();
|
||||
toast.success('Sector removed. Recalculate coverage to update.');
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Fix Priority
|
||||
|
||||
**Priority 1 (Critical):**
|
||||
- [ ] Fix cloneSector to add sector, not create site
|
||||
- [ ] Update button label to "+ Add Sector"
|
||||
- [ ] Clear coverage on sector delete
|
||||
|
||||
**Priority 2 (Important):**
|
||||
- [ ] Add warning for 800m + 300m combo
|
||||
- [ ] OR auto-adjust resolution based on radius
|
||||
|
||||
**Priority 3 (Nice to have):**
|
||||
- [ ] Clamp max radius based on resolution
|
||||
- [ ] Dynamic point sampling
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Clone Fix:
|
||||
1. Create site
|
||||
2. Click "+ Add Sector"
|
||||
3. Should show "Sites (1)" with 2 sectors ✅
|
||||
4. NOT "Sites (2)" ❌
|
||||
|
||||
### Coverage Gaps:
|
||||
1. Set resolution 300m
|
||||
2. Set radius 800m
|
||||
3. Calculate coverage
|
||||
4. At high zoom (16+), check for dots
|
||||
5. If dots visible → show warning OR auto-adjust
|
||||
|
||||
---
|
||||
|
||||
## Build & Deploy
|
||||
|
||||
```bash
|
||||
cd /opt/rfcp/frontend
|
||||
npm run build
|
||||
sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commit Message
|
||||
|
||||
```
|
||||
fix(sites): clone adds sector to existing site, not new site
|
||||
|
||||
- Fixed cloneSector to add sector to same site
|
||||
- Changed button label to "+ Add Sector"
|
||||
- Added coverage cache clear on sector delete
|
||||
- Sites count now accurate (counts sites, not sectors)
|
||||
|
||||
fix(coverage): prevent gaps with 800m radius
|
||||
|
||||
- Added warning for wide radius + coarse resolution
|
||||
- Auto-adjust resolution to radius/2 for smooth coverage
|
||||
- Clear coverage cache on sector changes
|
||||
```
|
||||
|
||||
🚀 Ready for 8.1!
|
||||
Reference in New Issue
Block a user