refactor: Complete architectural refactoring of qBRA plugin (Sprint 1–3 + Code Review)#32
Open
andures wants to merge 19 commits intoFLYGHT7:mainfrom
Open
refactor: Complete architectural refactoring of qBRA plugin (Sprint 1–3 + Code Review)#32andures wants to merge 19 commits intoFLYGHT7:mainfrom
andures wants to merge 19 commits intoFLYGHT7:mainfrom
Conversation
✅ Created refactor/main branch ✅ Configured mypy for type checking ✅ Configured pytest for testing ✅ Created tests/ directory structure ✅ Created baseline tests (10 passed, 3 skipped) ✅ Added requirements-dev.txt ✅ Improved .gitignore Files added: - .mypy.ini: Type checking configuration - pytest.ini: Test configuration - tests/__init__.py: Test package documentation - tests/conftest.py: Fixtures and test helpers - tests/test_baseline.py: Baseline smoke tests - requirements-dev.txt: Development dependencies - .gitignore: Enhanced patterns Test results: ✅ 10 passed, 3 skipped (QGIS not available) Next: Begin Sprint 1 - Story 1.1 (Type Hints)
… Python files - Added type hints to all functions and methods across 4 source files - Created __init__.py files for proper package structure (dockwidgets/, dockwidgets/ils/, modules/) - Added typing imports (Any, Optional, Dict, Tuple, Union) where needed - Fixed mypy type checking errors with explicit type annotations - Added docstrings to all public methods and functions Type Coverage: - qBRA/__init__.py: 100% typed (classFactory function) - qBRA/qbra_plugin.py: 100% typed (5 methods) - qBRA/dockwidgets/ils/ils_llz_dockwidget.py: 100% typed (10 methods + nested function) - qBRA/modules/ils_llz_logic.py: 100% typed (build_layers + pz helper) Validation: - mypy passes with 0 errors in strict mode (10 source files checked) - All calculation formulas preserved (no logic changes) Story 1.1: Type Hints - 6 hours (COMPLETED)
…parameters Replace Dict[str, Any] with strongly-typed dataclasses for improved type safety and validation. **New Models (qBRA/models/bra_parameters.py):** - FacilityDefaults: Default parameter values for facility types * Validates positive values, angle ranges * Enforces r/r_expr exclusivity - FacilityConfig: Configuration for facility types (LOC, LOCII, GP, DME) * Validates key/label presence * Enforces a_depends_on_threshold consistency - BRAParameters: All calculation parameters * Replaces Dict[str, Any] parameter passing * Validates azimuth (0-360°), direction (forward/backward) * Validates positive values for all dimensions * Auto-computes display_name if not provided * Includes to_dict() for backward compatibility **Refactored Files:** - qBRA/dockwidgets/ils/ils_llz_dockwidget.py: * _facility_defs now Dict[str, FacilityConfig] * _init_facility() creates FacilityConfig objects * _apply_facility_defaults() uses dataclass attributes * get_parameters() returns BRAParameters (with validation) - qBRA/modules/ils_llz_logic.py: * build_layers() accepts BRAParameters instead of Dict * Access params via attributes (params.a) not keys (params["a"]) * NO changes to calculation formulas (preserved as required) - qBRA/qbra_plugin.py: * Updated type annotations to use BRAParameters **Tests:** - Updated fixtures to return dataclass instances - Updated test assertions to use hasattr() for dataclasses - 8 tests passed, 5 skipped (QGIS imports - expected) **Validation:** - mypy: 0 errors in 12 source files ✓ - pytest: 8 passed, 5 skipped (expected) ✓ - All calculation formulas preserved ✓ Story 1.2: Dataclasses - 10 hours (COMPLETED)
Extract validation and layer logic from God Object dockwidget into specialized services. Services Created: - ValidationService: 11 pure validation methods (237 lines) - LayerService: 10 QGIS layer operations methods (177 lines) Test Coverage: - test_validation_service.py: 21 tests (177 lines) - test_layer_service.py: 7 tests (137 lines) - 8 tests pass, 26 skip when QGIS not available (expected) Refactored Components: - IlsLlzDockWidget.__init__: Dependency injection pattern - IlsLlzDockWidget.refresh_layers(): 35→12 lines (65% reduction) - IlsLlzDockWidget.get_parameters(): Manual validation→ValidationService Architecture: - Single Responsibility Principle applied - Separation of Concerns (UI/Logic/Data) - Pure functions in ValidationService (no side effects) - Fail-fast validation pattern Type Safety: - mypy --strict: 0 errors in 15 source files - Full type hints on all service methods Formula Protection: - Zero changes to ils_llz_logic.py calculations ✓ Design Patterns: - Dependency Injection - Service Layer - Pure Functions - Fail-Fast Validation Story Points: 8 SP Time Invested: 9 hours Skills Consulted: python-design-patterns, python-best-practices, pyqt6-ui-development-rules
Extract repeated feature creation logic into reusable function with declarative definitions. New Models: - FeatureDefinition: Frozen dataclass for BRA feature definition (42 lines) * Attributes: id, area, max_elev, area_name, geometry_points * Validation: id > 0, non-empty strings, >= 3 geometry points * Immutable (frozen=True) New Functions: - create_feature(): Generic feature creation from definition (27 lines) * Eliminates 7 duplicated blocks * Single source of truth for attributes * Consistent rounding logic (a, r to 2 decimals) Refactored Code: - build_layers(): 145 → 62 lines (57% reduction) * Declarative feature_definitions list (7 features) * List comprehension with create_feature() * Single addFeatures() call vs. 7 separate calls Test Coverage: - test_feature_definition.py: 8 tests (113 lines) * Valid construction, validation errors, immutability - test_ils_llz_logic.py: 3 tests (179 lines) * Feature creation, fallback logic, rounding Code Metrics: - 83 lines removed (145 → 62) - 7 duplicated blocks eliminated (100% reduction) - 292 lines of test code added - 86% reduction in setAttributes() calls (7 → 1) Formula Protection: - Zero changes to geometry calculations ✓ - Preserved point projections and elevations ✓ - Preserved rounding logic ✓ Design Patterns: - DRY (Don't Repeat Yourself) - Declarative configuration - Type-first development - Separation of concerns Story Points: 5 SP Time Invested: 3 hours Skills Consulted: python-design-patterns, python-best-practices
Fixed typo in test_create_feature_uses_facility_key_fallback method name (had extra space).
Replace print() statements with structured logging integrated with QGIS MessageLog. New Modules: - qBRA/utils/logging_config.py (131 lines) * QGISLogHandler: Custom handler for QGIS MessageLog integration * setup_logger(): Configure logger with formatting and handlers * get_logger(): Convenience function to get configured logger * Level mapping: Python logging → QGIS (Info/Warning/Critical) Logging Integration: - ils_llz_dockwidget.py: Replaced 3 print() statements * logger.debug() for azimuth calculations (with %.2f formatting) * logger.warning() for validation errors * logger.error() for unexpected errors (with exc_info=True for traceback) Test Coverage: - tests/test_logging_config.py (192 lines, 18 tests) * Test QGISLogHandler initialization and emit * Test setup_logger configuration and formatting * Test get_logger convenience function * Test logging integration with all levels (DEBUG/INFO/WARNING/ERROR) * Test exception logging with traceback Configuration: - Namespaced loggers: qBRA.dockwidgets.ils, qBRA.modules, etc. - Formatted output: timestamp - name - level - message - No propagation to root logger (isolated logging) - Graceful fallback when QGIS not available Documentation: - Updated REFACTORING_PLAN.md with Story 1.5 completion - Updated project progress: Sprint 1 now 88% complete (23/26 SP) Design Principles: - Python stdlib logging framework - QGIS integration via custom handler - Namespaced loggers per module - Structured formatting with timestamps - Exception logging with full traceback Story Points: 2 SP Time Invested: 2 hours Skills Consulted: python-best-practices (logging examples)
…plugin - Added custom exceptions: BRAError, BRAValidationError, BRACalculationError, LayerNotFoundError, and UIOperationError to improve error handling and clarity. - Updated validation and calculation logic in ils_llz_dockwidget.py and ils_llz_logic.py to raise specific exceptions. - Refactored logging in qbra_plugin.py to handle exceptions more gracefully and provide user-friendly messages. - Implemented lazy loading of model imports in models/__init__.py to avoid QGIS dependency during test discovery. - Enhanced logging configuration and tests to ensure proper logging behavior. - Added comprehensive tests for the new exception hierarchy to ensure robustness and correctness.
- Add README_DOCS.md, REFACTORING_PLAN.md, TIMELINE.md to .gitignore - Remove these files from git tracking (files remain locally) - These are working/planning documents not needed in repository
- Remove .agent/, .agents/, and .claude/ folders from git tracking - These directories contain local development tools and configurations - Files remain locally for development but are now properly ignored - 52 files removed from tracking as per .gitignore configuration
- Fix typo: TIMELINE.mdS -> TIMELINE.md in .gitignore - Remove TIMELINE.md from git tracking (file remains locally) - All documentation files now properly ignored
The file remains physically in the directory but is now ignored by git. This prevents local configuration from being tracked in version control.
- Updated logging configuration tests to correctly mock QgsMessageLog. - Improved validation service tests by directly importing necessary classes and enhancing mock layer creation. - Introduced a .coveragerc file to manage coverage reporting settings. - Created a new config module to centralize facility configuration definitions. - Added constants module to replace magic numbers and string literals. - Implemented a compatibility shim for Qt5/Qt6 to ensure consistent behavior across QGIS versions. - Developed background worker for BRA layer calculations to keep the UI responsive. - Added comprehensive unit tests for BRAParameters, FacilityConfig, and FacilityDefaults models. - Created integration tests for the BRA calculation flow to ensure end-to-end functionality. - Added tests for the Qt compatibility module to verify correct exports and version detection.
…ues in BRA parameters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Branch:
refactor/main→mainType: Refactoring + Bug fixes
🗣️ Executive Summary
The qBRA plugin calculates Building Restriction Areas around airport navigation aids. It was working, but its internal code was written as a quick proof-of-concept and had accumulated significant technical debt — making it fragile, hard to maintain, and risky to extend.
This work is the equivalent of renovating the foundation and plumbing of a building while keeping the rooms exactly as they are. From the outside, the plugin looks and works the same. From the inside:
All aeronautical formulas and geometry calculations are unchanged — this work touched architecture only.
📋 Description
This PR consolidates three complete refactoring sprints of the
qBRAplugin plus a code review round with bug fixes. The mathematical formulas and geometry calculation logic were not modified — only the architecture, code quality and type safety.🎯 Motivation
The original codebase was a single monolithic script with:
pt1,pt2, etc.)✅ Changes included
Sprint 1 — Core Refactoring (26 SP)
BRAParameters,FacilityConfig,FacilityDefaults(frozen)ValidationService,LayerServiceextracted from dockwidgetFeatureDefinition+create_feature()eliminate 7 duplicated blockslogging_config.pyintegrated with QGIS MessageLogBRACalculationError,ValidationError,LayerNotFoundError,UIOperationError,BRAValidationErrorSprint 2 — Testing & Quality (22 SP)
sys.modules, 0 tests skippedBRAParameters+ 21 tests forValidationServiceconstants.py,config.py, centralisedFACILITY_REGISTRYSprint 3 — Additional Improvements (20 SP)
ils_llz_logic.pypt_*variables → self-documenting descriptive namesBRAWorker(QThread)— UI never blocks during calculationutils/qt_compat.pyauto-detects Qt5/Qt6 and normalises dock area enumsCode Review — Bug fixes
All bugs identified in review using
python-best-practices,python-design-patternsandpyqt6-ui-development-rulesskills were resolved:unload()did not stop the worker → crash when unloading the pluginqbra_plugin.pyqbra_plugin.py_dockwithout None guard →AttributeErrorqbra_plugin.pylogging,BRACalculationError,UIOperationError)qbra_plugin.pyQgsProject,iface)ils_llz_dockwidget.pyAdditional bugs found during QGIS manual testing:
\"and literal\nintroduced by formatterValueErroron slope featureFeatureDefinitionfor the slope polygon was built withgeometry_points=[]b,h,r,D,H,L,phinow accept0.0— only negative values are rejected📁 New files
📊 Metrics
constants.py🧪 How to run tests
Expected result:
193 passed · 84.85% coverageImprovements identified in code review, non-blocking for merge:
BRAParametersshould befrozen=True_maybe_update_ruses hardcoded magic string"a+6000"defaultArea()return type is Qt5-specific after qt_compat introductionto_dict()is not called in production — potential dead code🔒 Security notes
exc_info=True) to aid diagnosisBRAParameters.__post_init__) and service layer (ValidationService) — double barrier