# FilterMate — General Deep Audit

**Date**: 2026-04-22
**Branch**: `main`
**Version**: `4.6.6`
**Auditor**: Claude (tech-lead-gis)
**Previous audit**: [2026-02-11](../../.serena/memories/audit_filtermate_2026_02_11.md) — score 9.0/10
**Current score**: **8.0 / 10** (slight regression on hygiene, offset by feature/security wins)

---

## 1. Executive summary

FilterMate has shipped substantial value in the last 10 weeks — 153 commits, an extension system, a public API, 12 new languages, security hardening, and live config migration. But technical debt is creeping back: the two god classes that were brought under control in February have grown again, two test failures remain unresolved since the last audit, and the repo now tracks ~3 MB of unrelated `video_automation/` + `video_toolkit/` sources that have no business in a QGIS plugin distribution.

**Top 3 risks going into the next release cycle**:

1. `filter_mate_dockwidget.py` drift: +409 LOC since February (now 6 889 lines).
2. `core/tasks/filter_task.py` crossed the <3000 LOC target again (3 003 lines).
3. Raster roadmap is blocked — merged twice, reverted twice. Uncertainty in backlog.

**Top 3 recommendations**:

1. Remove `video_automation/` + `video_toolkit/` + `debug_gpkg_project.py` from the plugin repo (one-day cleanup).
2. Fix the 2 lingering test-pollution failures before v4.7.
3. Finish the PROVIDER_* string-literal migration (37 files, mechanical).

---

## 2. Scorecard

| Dimension | Score | Trend | Notes |
|---|:---:|:---:|---|
| **Architecture** | 9/10 | → | Hexagonal, clean layer separation, extension system added |
| **Test coverage** | 8/10 | → | 720/722 green, CI active, 2 stuck failures |
| **Code size / complexity** | 6.5/10 | ↓ | 2 hotspots regressed, 24 files >1000 LOC |
| **Code hygiene** | 7/10 | ↓ | 3 MB out-of-scope code tracked |
| **i18n** | 10/10 | ↑ | 34 languages, live switching works |
| **Security** | 9/10 | ↑ | Bandit findings fixed this quarter |
| **Documentation** | 8/10 | → | Solid memories + BMAD + Copilot |
| **CI/CD** | 9/10 | ↑ | Tests + release workflow live |
| **Backlog discipline** | 7/10 | ↓ | Raster revert cycle, PROVIDER_* stalled |
| **Overall** | **8.0/10** | ↓ | |

---

## 3. Git activity (2026-02-11 → 2026-04-22)

- **Commits**: 153 on `main`
- **Releases**: `4.6.2` → `4.6.3` → `4.6.4` → `4.6.5` → `4.6.6`
- **Major merges**:
  - `93ae89c` — feat: FilterMatePublicAPI for inter-plugin communication
  - `dd4ef97` — Merge `feat/extension-system-qfieldcloud`
  - `a01e04f` — Merge `delegate/filtermate/1774367464` (PublicAPI)
  - `7dd9332` — Merge `refactor/quick-wins-2026-02-10` (handlers, tests, raster UI)
  - `517b66b` — **Revert** of the above (raster UI rolled back)
- **Reverts this cycle**: 2 (raster UI merge, feature picker filter reset)

### Working tree state
Uncommitted WIP on `main`:
- [core/services/app_initializer.py](../../core/services/app_initializer.py) (+26/-14)
- [filter_mate_dockwidget_base.ui](../../filter_mate_dockwidget_base.ui) (+3/-3)
- [resources/styles/default.qss](../../resources/styles/default.qss) (+18/-35)

**Action**: confirm these are in progress before next release — or stash/commit.

---

## 4. Codebase metrics

### 4.1 Overall

| Metric | Value | Δ vs 2026-02-11 |
|---|---:|---:|
| Total Python files (plugin scope) | 396 | — |
| Total LOC (plugin scope) | 172 085 | stable |
| Tests collected | 722 | +122 |
| Test files | 23 | -11 (consolidated) |
| TODO/FIXME markers | 5 | clean |
| `except Exception:` occurrences | 240 | too broad |

### 4.2 Layer distribution

| Layer | Files | LOC | Share |
|---|---:|---:|---:|
| core | 116 | 54 196 | 31.5% |
| ui | 61 | 35 516 | 20.6% |
| adapters | 76 | 33 332 | 19.4% |
| infrastructure | 43 | 14 762 | 8.6% |
| extensions | 14 | 3 069 | 1.8% |
| config | 7 | 2 102 | 1.2% |
| filtermate_api | 5 | 263 | 0.2% |
| root entry points | ~10 | ~14 000 | 8.1% |
| other (scripts, website, utils) | ~64 | ~14 845 | 8.6% |

Hexagonal architecture holds: `core` and `adapters` stay clean of Qt imports except where intentional. UI share has grown (controllers added) but within acceptable range.

### 4.3 Core subdivision

| Subdirectory | LOC |
|---|---:|
| core/services | 16 355 |
| core/tasks | 15 935 |
| core/filter | 5 104 |
| core/ports | 4 372 |
| core/optimization | 3 030 |
| core/domain | 2 587 |
| core/export | 2 556 |
| core/geometry | 2 120 |
| core/strategies | 2 001 |

---

## 5. God classes — files > 1 000 LOC

**24 files** exceed 1 000 lines. Regressions and new hotspots:

| # | File | LOC | 2026-02-11 | Δ | Status |
|--:|---|---:|---:|---:|:---:|
| 1 | [filter_mate_dockwidget.py](../../filter_mate_dockwidget.py) | **6 889** | 6 480 | **+409** | ⚠️ regression |
| 2 | [ui/controllers/exploring_controller.py](../../ui/controllers/exploring_controller.py) | **3 253** | — | new | ⚠️ new hotspot |
| 3 | [ui/controllers/integration.py](../../ui/controllers/integration.py) | **3 011** | — | new | ⚠️ new hotspot |
| 4 | [core/tasks/filter_task.py](../../core/tasks/filter_task.py) | **3 003** | 2 929 | **+74** | ⚠️ crossed target |
| 5 | [filter_mate_app.py](../../filter_mate_app.py) | 2 671 | ~2 670 | — | stable |
| 6 | [core/tasks/layer_management_task.py](../../core/tasks/layer_management_task.py) | 1 914 | — | — | stable |
| 7 | [adapters/backends/postgresql/expression_builder.py](../../adapters/backends/postgresql/expression_builder.py) | 1 629 | — | — | stable |
| 8 | [ui/controllers/favorites_controller.py](../../ui/controllers/favorites_controller.py) | 1 622 | — | — | stable |
| 9 | [core/optimization/combined_query_optimizer.py](../../core/optimization/combined_query_optimizer.py) | 1 596 | — | — | stable |
| 10 | [ui/widgets/custom_widgets.py](../../ui/widgets/custom_widgets.py) | 1 577 | — | — | stable |
| 11 | [core/filter/expression_builder.py](../../core/filter/expression_builder.py) | 1 572 | — | — | stable |
| 12 | [filter_mate.py](../../filter_mate.py) | 1 549 | — | — | entry point |
| 13 | [ui/controllers/filtering_controller.py](../../ui/controllers/filtering_controller.py) | 1 493 | — | — | stable |
| 14 | [ui/controllers/backend_controller.py](../../ui/controllers/backend_controller.py) | 1 397 | — | — | stable |
| 15 | [core/services/favorites_service.py](../../core/services/favorites_service.py) | 1 302 | — | — | stable |
| 16 | [ui/controllers/layer_sync_controller.py](../../ui/controllers/layer_sync_controller.py) | 1 269 | — | — | stable |
| 17 | [ui/controllers/property_controller.py](../../ui/controllers/property_controller.py) | 1 268 | — | — | stable |
| 18 | [infrastructure/utils/layer_utils.py](../../infrastructure/utils/layer_utils.py) | 1 251 | — | — | stable |
| 19 | [adapters/backends/spatialite/filter_executor.py](../../adapters/backends/spatialite/filter_executor.py) | 1 225 | — | — | stable |
| 20 | [adapters/qgis/geometry_preparation.py](../../adapters/qgis/geometry_preparation.py) | 1 203 | — | — | stable |
| 21 | [adapters/backends/ogr/filter_executor.py](../../adapters/backends/ogr/filter_executor.py) | 1 155 | — | — | stable |
| 22 | [adapters/backends/postgresql/filter_executor.py](../../adapters/backends/postgresql/filter_executor.py) | 1 055 | — | — | stable |
| 23 | [adapters/task_builder.py](../../adapters/task_builder.py) | 1 052 | — | — | stable |
| 24 | [core/strategies/multi_step_filter.py](../../core/strategies/multi_step_filter.py) | 1 046 | — | — | stable |

**Interpretation**: the Phase 2 dockwidget extraction (deferred in February) is now overdue. The two new UI controllers (`exploring_controller.py`, `integration.py`) show that significant logic moved *out* of the dockwidget — but the dockwidget still grew in absolute terms.

---

## 6. Tests

### 6.1 Status
- **Collected**: 722
- **Passed**: 720
- **Failed**: 2 — [tests/unit/core/tasks/test_handlers.py](../../tests/unit/core/tasks/test_handlers.py):
  - `TestSubsetManagementHandler::test_determine_backend_postgresql`
  - `TestSubsetManagementHandler::test_get_spatialite_datasource_fallback`
- **Failure mode**: **pollution** — both pass in isolation (`pytest tests/unit/core/tasks/test_handlers.py::TestSubsetManagementHandler` → 16/16 green), fail only when the full suite runs.
- **Age of failure**: already present at the 2026-02-11 audit. **69 days open.**

### 6.2 Test distribution

| Test file | Tests |
|---|---:|
| test_domain_models.py | 110 |
| test_handlers.py | 76 |
| test_filtering_controller.py | 56 |
| test_filter_executor.py (OGR) | 42 |
| test_expression_builder.py (PG) | 39 |
| test_exceptions.py | 37 |
| test_layer_sync_controller.py | 34 |
| test_sql_utils.py | 33 |
| test_signal_manager.py | 32 |
| test_schema_manager.py | 32 |
| test_expression_builder.py (SL) | 31 |
| test_cleanup.py (PG) | 24 |
| test_expression_builder.py (core) | 23 |
| test_qfieldcloud_adapter.py | 21 |
| test_favorites_controller.py | 20 |
| *(and 8 more)* | — |

### 6.3 Coverage gaps
- **UI controllers**: 5/12 covered (exploring, favorites, filtering, layer_sync — partial)
- **Core services**: only `favorites_service` has a dedicated test
- **Dockwidget**: no unit tests (god class — as expected)
- **Extensions/qfieldcloud**: well covered (3 test files)

### 6.4 CI
[.github/workflows/test.yml](../../.github/workflows/test.yml) runs pytest on Python 3.10 + 3.12 on every push/PR to `main`/`develop`. Release workflow `.github/workflows/release.yml` is live.

**Action**: fix the 2 pollution failures or CI shows red on main.

---

## 7. Code quality signals

### 7.1 Excellent
- TODO/FIXME count: **5** (all tracked with context).
- Bare `except:` clauses: **0**.
- Raw `blockSignals()` outside `SignalBlocker`: **4** (down from 24+).
- Syntax errors: **1** (only `debug_gpkg_project.py`, should be removed).

### 7.2 Needs attention

**`except Exception:` — 240 occurrences**, distribution:

| File | Count |
|---|---:|
| filter_mate_dockwidget.py | 30 |
| infrastructure/feedback/__init__.py | 10 |
| infrastructure/database/connection_pool.py | 9 |
| adapters/backends/ogr/expression_builder.py | 8 |
| core/services/canvas_refresh_service.py | 7 |
| adapters/qgis/geometry_preparation.py | 7 |
| ui/controllers/exporting_controller.py | 6 |
| infrastructure/utils/layer_utils.py | 6 |
| ui/controllers/backend_controller.py | 5 |
| infrastructure/utils/__init__.py | 5 |
| core/geometry/geometry_safety.py | 5 |

Not all are wrong — error-reporting layers and Qt signal callbacks legitimately need broad catches — but 240 is a code smell for a 172k-LOC project. **Recommend**: tighten exception types in dockwidget signal handlers (30 is high for one file).

### 7.3 `print()` outside tests (85)
- Legitimate: `website/extract_diagrams.py` (8), `scripts/` utilities, `config/config_validator.py:54` (CLI validator).
- **Should be removed**: `debug_gpkg_project.py` (tracked debug script with hardcoded Windows path).

### 7.4 Deprecated markers (~30)
All properly documented with version tags (`v3.0.0`, `v4.0.0`, `v5.0`). The TODO v5.0 markers in [filter_mate_dockwidget.py:996](../../filter_mate_dockwidget.py#L996), [core/tasks/filter_task.py:2352](../../core/tasks/filter_task.py#L2352), [ui/layout/dimensions_manager.py:200](../../ui/layout/dimensions_manager.py#L200) should be part of a v5.0 planning discussion — are we shipping v5.0 soon?

---

## 8. PROVIDER_* migration (carry-over from 2026-02-11 backlog)

### 8.1 State
- Centralized constants defined in [infrastructure/constants.py:15-36](../../infrastructure/constants.py#L15-L36) — clean, with `PROVIDER_TYPE_MAPPING` dict for normalization.
- Files **using constants**: 16
- Files **still using string literals**: 37
- Progress since 2026-02-11: **marginal** (5 files migrated vs promised ~55).

### 8.2 Worst offenders

| File | String-literal occurrences |
|---|---:|
| [ui/controllers/backend_controller.py](../../ui/controllers/backend_controller.py) | 12 |
| [core/services/backend_service.py](../../core/services/backend_service.py) | 9 |
| [infrastructure/parallel/parallel_executor.py](../../infrastructure/parallel/parallel_executor.py) | 7 |
| [core/services/layer_lifecycle_service.py](../../core/services/layer_lifecycle_service.py) | 7 |
| [ui/widgets/backend_indicator.py](../../ui/widgets/backend_indicator.py) | 4 |
| [infrastructure/utils/layer_utils.py](../../infrastructure/utils/layer_utils.py) | 4 |
| [infrastructure/database/style_filter_fixer.py](../../infrastructure/database/style_filter_fixer.py) | 4 |
| [infrastructure/database/sql_utils.py](../../infrastructure/database/sql_utils.py) | 4 |
| [core/tasks/layer_management_task.py](../../core/tasks/layer_management_task.py) | 4 |
| [core/services/canvas_refresh_service.py](../../core/services/canvas_refresh_service.py) | 4 |
| [adapters/layer_validator.py](../../adapters/layer_validator.py) | 4 |

### 8.3 Risk
String literals are grep-fragile: typos (`'postgre'` vs `'postgres'`) compile silently. `backend_controller.py` has 12 of them in a critical decision path — a typo here could route queries to the wrong backend.

**Action**: run a one-commit mechanical migration sweep. Low risk, high value.

---

## 9. Hygiene findings (new since last audit)

### 9.1 ⚠️ Out-of-scope code in plugin repo

230 tracked files live under:
- [video_automation/](../../video_automation/) — ~3 MB (standalone narration/video pipeline, Dockerfile, `.wav` file)
- [video_toolkit/](../../video_toolkit/) — reusable video toolkit package

Neither is imported by FilterMate. `.gitignore` does **not** exclude them. They will ship inside `filter_mate_v4.6.7.zip` when you next package the plugin, inflating the download from its expected ~5 MB.

**Action**: `git rm -r --cached video_automation/ video_toolkit/`, add to `.gitignore`, relocate sources to a separate repo (e.g., `narractive/video_toolkit`). Verify no `filter_mate.py` imports reference them.

### 9.2 ⚠️ Debug script at repo root
[debug_gpkg_project.py](../../debug_gpkg_project.py):
- Tracked in git
- Raises `SyntaxWarning: invalid escape sequence '\p'` on parse (`r"C:\path\to\your\exported.gpkg"` missing raw string)
- Hardcoded `C:\path\to\your\exported.gpkg` placeholder — clearly a throwaway dev script

**Action**: `git rm debug_gpkg_project.py`.

### 9.3 ⚠️ Empty post-revert directory
[extensions/raster/](../../extensions/raster/) had no tracked sources — only orphaned `__pycache__/*.pyc` files from the reverted `7dd9332` merge. Cleaned locally during this audit (not committed):
```
rm -rf extensions/raster/__pycache__ extensions/raster/ui/__pycache__ extensions/raster
```

**Action**: nothing tracked, so no commit needed — but if the raster work was meant to land, the state needs clarifying.

### 9.4 Raster roadmap instability
- `b2e8b54` — Merge raster v9 integration
- `1664d65` — Revert raster v9 integration
- `7dd9332` — Merge raster UI phase 2
- `517b66b` — Revert raster UI phase 2

Two full merge-and-revert cycles in 3 months. The [BACKLOG_RASTER_POINTCLOUD_V1.md](../../BACKLOG_RASTER_POINTCLOUD_V1.md) is 21 KB of planning; the reality is zero delivered raster code on `main`.

**Action**: product decision — commit fully or drop from v4.x backlog and push to v5.0.

### 9.5 Serena memory sprawl
- `.serena/memories/`: 28 memories, 292 KB
- Several pre-date v4.0 and describe state/decisions no longer valid:
  - `action_plan_v5.0` — has v5.0 shipped? No.
  - `merge_analysis_v9_integration_2026_02_09` — reverted, obsolete.
  - `clipguard_project_decisions_2026_02_16` — another project leaking in.
  - `c1_filter_task_decomposition_2026_02_11` — superseded.
  - `p2_2_god_class_decomposition_2026_02_11` — superseded.

**Action**: archive or delete obsolete memories. Keep `audit_*`, `backlog_*`, `code_style`, `project_overview`, `suggested_commands`, `testing_documentation`, `ui_system`.

### 9.6 Uncommitted WIP
See §3. Clarify before next release build.

---

## 10. Improvements shipped since 2026-02-11 ✅

### 10.1 Features
- **Extension system**: clean registry pattern in [extensions/registry.py](../../extensions/registry.py), `BaseExtension` in [extensions/base.py](../../extensions/base.py), lifecycle hooks (`discover → init → UI → teardown`).
- **QFieldCloud extension** (3 069 LOC): credentials manager, SDK adapter, project builder, push dialog, settings dialog.
- **FilterMatePublicAPI** ([filtermate_api/](../../filtermate_api/), 263 LOC) for inter-plugin communication (PRs #14-17).
- **Live config tree** with auto-migration (`0a47ce2`).
- **Feature picker improvements**: FID-based debounce, next/prev browser buttons, display-field refresh.

### 10.2 i18n
- 22 → **34 languages** (`cb6d4b2`)
- **79 new translations** (`d5f1ae9`) including QFieldCloud strings
- Live language switching fixed — multiple iterations (`d880d6b`, `4c3afa2`, `8ff8169`, `e3c853e`, `66a1080`, `c07bf5d`)

### 10.3 Security
- `763e9a1` — Bandit XML + mktemp vulnerabilities fixed
- `869567c` — v4.6.6 security scan release

### 10.4 Bug fixes (recent, noteworthy)
- `b5c89f7` — use `pointOnSurface()` not `centroid()` for spatial filtering (matches memory pitfall note)
- `021575a` — favorites deep audit (filter apply, project persistence, manager dialog)
- `f769f13` — export: sidecar style naming, LIBKML groups, OGR driver case preservation
- `ba87b9c` — exploring toolbar visibility + combobox text clipping
- `98686b6` — favorites groupbox mode survives project reopen

### 10.5 Tooling
- CI pytest workflow ([.github/workflows/test.yml](../../.github/workflows/test.yml)) active
- Automatic release workflow ([.github/workflows/release.yml](../../.github/workflows/release.yml))
- BMAD integration for product planning (`_bmad/`)

---

## 11. Degradations since 2026-02-11 ❌

| # | Issue | Severity |
|:--:|---|:--:|
| 1 | `filter_mate_dockwidget.py` +409 LOC | ⚠️ medium |
| 2 | `filter_task.py` back above 3 000 LOC target | ⚠️ medium |
| 3 | 2 test failures persist (69 days open) | ⚠️ medium |
| 4 | PROVIDER_* migration stalled (37 files unchanged) | low |
| 5 | `video_automation/` + `video_toolkit/` in plugin repo | ⚠️ medium |
| 6 | Raster roadmap merge-revert cycle | ⚠️ medium |
| 7 | `debug_gpkg_project.py` still tracked | low |
| 8 | Serena memories not pruned | low |

---

## 12. Recommendations — ordered by ROI

### 12.1 Sprint 0 (cleanup, 1 day)
1. `git rm -r --cached video_automation/ video_toolkit/` + update `.gitignore` + relocate to separate repo.
2. `git rm debug_gpkg_project.py`.
3. Commit or stash the 3 working-tree changes on `main`.
4. Prune obsolete Serena memories (§9.5).

### 12.2 Sprint 1 (stability, 2-3 days)
5. Fix the 2 test-pollution failures in `test_handlers.py::TestSubsetManagementHandler`.
6. Finish PROVIDER_* migration in the 37 remaining files (start with `backend_controller.py`, `backend_service.py`).
7. Tighten `except Exception:` in `filter_mate_dockwidget.py` (30 → target <10).

### 12.3 Sprint 2 (refactor, 1-2 weeks)
8. **Dockwidget Phase 2**: extract the 409 new LOC into managers. Target <6 000 LOC.
9. **filter_task.py cleanup**: get back below 3 000 LOC by extracting 1-2 more handlers.
10. Raster roadmap **decision**: commit to Sprint 0 of [BACKLOG_RASTER_POINTCLOUD_V1.md](../../BACKLOG_RASTER_POINTCLOUD_V1.md) or formally defer to v5.0.

### 12.4 Sprint 3+ (nice-to-have)
11. Add tests for the 7 uncovered UI controllers (exporting, backend, property, etc.).
12. Add integration tests for the extension system lifecycle.
13. Document the PublicAPI surface for plugin integrators.

---

## 13. References

- Previous audit: [`.serena/memories/audit_filtermate_2026_02_11.md`](../../.serena/memories/audit_filtermate_2026_02_11.md)
- Backlog raster: [`BACKLOG_RASTER_POINTCLOUD_V1.md`](../../BACKLOG_RASTER_POINTCLOUD_V1.md)
- Config harmonization: [`.serena/memories/config_harmonization_2026_02_19.md`](../../.serena/memories/config_harmonization_2026_02_19.md)
- CLAUDE.md: [`../../CLAUDE.md`](../../CLAUDE.md)

---

*Report produced 2026-04-22 by Claude (tech-lead-gis). Methodology: git log analysis, LOC measurement via `wc -l`, AST parse for syntax checks, pytest run, grep-based pattern inventory, cross-reference with 2026-02-11 memory baseline.*
