Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/qiki/services/operator_console/main_orion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1808,11 +1808,11 @@ def __init__(self) -> None:
width_cells=self._ppi_width_cells,
height_cells=self._ppi_height_cells,
max_range_m=self._ppi_max_range_m,
)
) # type: ignore[assignment]
elif PpiScopeRenderer is not None:
self._ppi_renderer = PpiScopeRenderer(width=ppi_width, height=ppi_height, max_range_m=ppi_max_range)
self._ppi_renderer = PpiScopeRenderer(width=ppi_width, height=ppi_height, max_range_m=ppi_max_range) # type: ignore[assignment]
else:
self._ppi_renderer = None
self._ppi_renderer = None # type: ignore[assignment]
Comment on lines +1811 to +1815

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Проверка масштаба проблемы и точек применения после рефактора.

# 1) Все места с Any-кастами/assignment-ignore вокруг PPI.
rg -n "cast\(Any,\s*ppi\)\.update|_ppi_renderer\s*=.*type:\s*ignore\[assignment\]" src/qiki/services/operator_console/main_orion.py

# 2) Все обращения к _ppi_renderer (для проверки, что контракт стал единообразным).
rg -n "self\._ppi_renderer" src/qiki/services/operator_console/main_orion.py -C 2

Repository: sonra44/QIKI_DTMP

Length of output: 3320


🏁 Script executed:

# Контекст переменной ppi и её объявления/типов
# 1) Ищем, где объявляется/инициализируется переменная ppi в методах, где она используется
rg -B 10 "cast\(Any,\s*ppi\)\.update" src/qiki/services/operator_console/main_orion.py | head -80

# 2) Ищем тип этой переменной через аннотации
rg "ppi\s*[:=]" src/qiki/services/operator_console/main_orion.py | grep -E "ppi\s*:" | head -20

Repository: sonra44/QIKI_DTMP

Length of output: 3481


🏁 Script executed:

# Найти определение UI элемента с id "radar-ppi" и его полный тип
rg -n "#radar-ppi|compose|radar_ppi" src/qiki/services/operator_console/main_orion.py | head -30

# Получить полный контекст методов _update_radar_display и _seed_radar_ppi
rg -A 50 "def _update_radar_display\(self\)" src/qiki/services/operator_console/main_orion.py | head -70

# Получить импорты и базовый класс
head -50 src/qiki/services/operator_console/main_orion.py

Repository: sonra44/QIKI_DTMP

Length of output: 3655


🏁 Script executed:

# Найти в compose методах создание виджета `#radar-ppi`
rg -B 5 -A 5 'id="radar-ppi"' src/qiki/services/operator_console/main_orion.py

# Получить полный метод _render_radar_ppi
sed -n '2520,2620p' src/qiki/services/operator_console/main_orion.py

Repository: sonra44/QIKI_DTMP

Length of output: 6932


🏁 Script executed:

# Найти импорты классов рендеров
rg "from.*import.*RadarPpi|from.*import.*RadarBitmap" src/qiki/services/operator_console/main_orion.py

# Найти где определены эти классы
fd -name "*.py" -type f src/qiki/services/operator_console | xargs rg -l "class RadarPpi|class RadarBitmapTGP|class RadarBitmapSixel|class RadarBitmapAuto" 2>/dev/null | head -10

Repository: sonra44/QIKI_DTMP

Length of output: 357


🏁 Script executed:

# Получить все импорты в main_orion.py, ищем RadarPpi
rg "RadarPpi|RadarBitmap" src/qiki/services/operator_console/main_orion.py | grep -E "^[0-9]+:(from|import)" | head -20

# Найти все импорты в начале файла
head -100 src/qiki/services/operator_console/main_orion.py | grep -E "from.*import|^import"

# Поиск где эти классы определены или импортируются
rg -n "^from|^import|RadarPpi|RadarBitmapTGP|RadarBitmapSixel|RadarBitmapAuto" src/qiki/services/operator_console/main_orion.py | grep -E "(from|import|Radar)" | head -30

Repository: sonra44/QIKI_DTMP

Length of output: 3517


🏁 Script executed:

# Поиск где определяются эти классы (присваивание или условный импорт)
rg -n "RadarPpi\s*=" src/qiki/services/operator_console/main_orion.py | head -10

# Найти все упоминания RadarPpi
rg -n "RadarPpi" src/qiki/services/operator_console/main_orion.py | head -20

# Получить контекст вокруг строк 100-200 (где могут быть условные импорты)
sed -n '100,300p' src/qiki/services/operator_console/main_orion.py | grep -A 3 -B 3 "RadarPpi\|RadarBitmap"

Repository: sonra44/QIKI_DTMP

Length of output: 400


🏁 Script executed:

# Найти все четыре класса рендеров
rg -n "class RadarBitmapTGP|class RadarBitmapSixel|class RadarBitmapAuto|RadarBitmapTGP\s*=|RadarBitmapSixel\s*=|RadarBitmapAuto\s*=" src/qiki/services/operator_console/main_orion.py

# Получить определение RadarPpi класса (чтобы увидеть его методы)
sed -n '1190,1250p' src/qiki/services/operator_console/main_orion.py

Repository: sonra44/QIKI_DTMP

Length of output: 3467


🏁 Script executed:

# Проверить есть ли у Static метод update() в Textual
rg -n "def update" src/qiki/services/operator_console/main_orion.py | grep -E "(Static|update.*)" | head -10

# Посмотреть как именно вызывается .update в контексте и на каких параметрах
rg -B 2 -A 2 "\.update\(" src/qiki/services/operator_console/main_orion.py | grep -A 2 "cast(Any, ppi).update" | head -20

Repository: sonra44/QIKI_DTMP

Length of output: 1043


Замените cast(Any, ppi) и # type: ignore[assignment] на явный Protocol для рендера радара.

Типизация "чинится" локальными подавлениями в 8+ местах (строки 2562, 2595, 2615, 5955, 5962, 5985, 5992, 6010 и 1811–1815), что размывает гарантии статического анализа в критическом UI-пути. Вместо этого используйте узкий Protocol для ppi объекта и удалите cast(Any, ...) + # type: ignore.

💡 Предлагаемый рефактор
+from typing import Protocol
+
+class _RadarPpiRenderable(Protocol):
+    """Widget с методом update для рендеринга радара."""
+    def update(self, renderable: object) -> None: ...

Затем в методе инициализации:

         if BraillePpiRenderer is not None:
             self._ppi_renderer = BraillePpiRenderer(
                 width_cells=self._ppi_width_cells,
                 height_cells=self._ppi_height_cells,
                 max_range_m=self._ppi_max_range_m,
-            ) # type: ignore[assignment]
+            )
         elif PpiScopeRenderer is not None:
-            self._ppi_renderer = PpiScopeRenderer(width=ppi_width, height=ppi_height, max_range_m=ppi_max_range) # type: ignore[assignment]
+            self._ppi_renderer = PpiScopeRenderer(width=ppi_width, height=ppi_height, max_range_m=ppi_max_range)
         else:
-            self._ppi_renderer = None # type: ignore[assignment]
+            self._ppi_renderer = None

И во всех местах, где используется ppi:

-            cast(Any, ppi).update(I18N.bidi("Radar display unavailable", "Экран радара недоступен"))
+            ppi.update(I18N.bidi("Radar display unavailable", "Экран радара недоступен"))
-            cast(Any, ppi).update(self._ppi_renderer.render_tracks(payloads))
+            ppi.update(self._ppi_renderer.render_tracks(payloads))

Это соответствует guideline: src/**/*.py — избегайте Any вне boundaries (JSON/NATS/proto сообщения).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qiki/services/operator_console/main_orion.py` around lines 1811 - 1815,
Добавьте явный Protocol (напр., PpiRendererProtocol) описывающий обязательные
методы/атрибуты, используемые в рендерере радара (используйте те же имена
методов, что встречаются в коде вокруг self._ppi_renderer и PpiScopeRenderer),
затем замените все cast(Any, ppi) и присвоения с “# type: ignore[assignment]” на
аннотацию типа Optional[PpiRendererProtocol] (включая атрибут self._ppi_renderer
и параметры/локальные переменные, где ppi используется) и приведите места
создания к соответствующему типу (например, при PpiScopeRenderer(...)).
Убедитесь, что Protocol экспортирован/импортирован там, где нужно, и удалите все
локальные # type: ignore[assignment] в перечисленных местах (строки, где
встречались подавления: места вокруг использования ppi и PpiScopeRenderer).

self._update_system_snapshot()
self._load_incident_rules(initial=True)

Expand Down Expand Up @@ -2559,7 +2559,7 @@ def _render_radar_ppi(self) -> None:
payloads = [payload for _tid, payload, _seen in tracks_items]
if self._ppi_renderer is None:
try:
ppi.update(I18N.bidi("Radar display unavailable", "Экран радара недоступен"))
cast(Any, ppi).update(I18N.bidi("Radar display unavailable", "Экран радара недоступен"))
except Exception:
logger.debug("orion_exception_swallowed", exc_info=True)
return
Expand Down Expand Up @@ -2612,7 +2612,7 @@ def _render_radar_ppi(self) -> None:
self._render_radar_legend()
return
try:
ppi.update(self._ppi_renderer.render_tracks(payloads))
cast(Any, ppi).update(self._ppi_renderer.render_tracks(payloads))
except Exception:
return
self._render_radar_legend()
Expand Down Expand Up @@ -6007,7 +6007,7 @@ def _seed_radar_ppi(self) -> None:
self._render_radar_legend()
return
try:
ppi.update(self._ppi_renderer.render_tracks([]))
cast(Any, ppi).update(self._ppi_renderer.render_tracks([]))
except Exception:
return
self._render_radar_legend()
Expand Down
1 change: 0 additions & 1 deletion tests/integration/test_power_soc_dynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import json
import os

import asyncio
import nats
import pytest
from nats.errors import NoServersError, TimeoutError as NatsTimeoutError
Expand Down
1 change: 0 additions & 1 deletion tests/integration/test_thermal_core_trip_event.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import json
import os

Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_power_load_shedding_order.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest

from qiki.services.q_sim_service.core.world_model import WorldModel

Expand Down
Loading