analysis_service.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from __future__ import annotations
  2. from dataclasses import dataclass
  3. from typing import Any, Dict, Optional
  4. import requests
  5. from config import ANALYSIS_BASE_URL, HTTP_TIMEOUT_SEC, get_endpoint, join_url
  6. @dataclass(frozen=True)
  7. class AnalysisExport:
  8. """Опциональный тип для результатов export/plot API (зависит от бэкенда)."""
  9. payload: Dict[str, Any]
  10. class AnalysisService:
  11. """Client for analysis microservice.
  12. Переносит функциональность прежнего analysis_api.py.
  13. Все PATH берём из config.ENDPOINTS["analysis"].
  14. """
  15. def __init__(self, base_url: str | None = None, timeout: float | None = None) -> None:
  16. self.base = (base_url or ANALYSIS_BASE_URL).rstrip("/")
  17. self.timeout = float(timeout if timeout is not None else HTTP_TIMEOUT_SEC)
  18. self.session = requests.Session()
  19. def _url(self, endpoint_key: str) -> str:
  20. path = get_endpoint("analysis", endpoint_key)
  21. return join_url(self.base, path)
  22. def _post_with_session_fallback(
  23. self,
  24. endpoint_key: str,
  25. session_id: str,
  26. extra_form: Optional[Dict[str, Any]] = None,
  27. ) -> Dict[str, Any]:
  28. """POST endpoint with session_id.
  29. Some backend versions expect session_id as form field, others as query param.
  30. Try form first; if validation fails (422), retry with query params.
  31. """
  32. url = self._url(endpoint_key)
  33. payload: Dict[str, Any] = {"session_id": session_id}
  34. if extra_form:
  35. payload.update(extra_form)
  36. r = self.session.post(url, data=payload, timeout=self.timeout)
  37. if r.status_code == 422:
  38. r = self.session.post(url, params=payload, timeout=self.timeout)
  39. r.raise_for_status()
  40. return r.json() if r.content else {}
  41. def upload(self, file_path: str) -> str:
  42. url = self._url("upload")
  43. with open(file_path, "rb") as f:
  44. r = self.session.post(url, files={"file": f}, timeout=self.timeout)
  45. r.raise_for_status()
  46. j = r.json() if r.content else {}
  47. if "session_id" not in j:
  48. raise ValueError(f"analysis.upload(): missing 'session_id' in response: {j}")
  49. return str(j["session_id"])
  50. def filter(
  51. self,
  52. session_id: str,
  53. dt: float,
  54. center: float,
  55. lo: float,
  56. hi: float,
  57. low: float,
  58. ) -> None:
  59. url = self._url("filter")
  60. data = {
  61. "session_id": session_id,
  62. "dt": dt,
  63. "center_freq": center,
  64. "lower_freq": lo,
  65. "higher_freq": hi,
  66. "low_freq": low,
  67. }
  68. r = self.session.post(url, data=data, timeout=self.timeout)
  69. r.raise_for_status()
  70. def fft(
  71. self,
  72. session_id: str,
  73. c1: Optional[int] = None,
  74. c2: Optional[int] = None,
  75. c3: Optional[int] = None,
  76. c4: Optional[int] = None,
  77. ) -> None:
  78. url = self._url("fft")
  79. data = {"session_id": session_id}
  80. if c1 is not None and c2 is not None and c3 is not None and c4 is not None:
  81. data.update(
  82. {
  83. "coef_dec_1": c1,
  84. "coef_dec_2": c2,
  85. "coef_dec_3": c3,
  86. "coef_dec_4": c4,
  87. }
  88. )
  89. r = self.session.post(url, data=data, timeout=self.timeout)
  90. r.raise_for_status()
  91. def result(self, session_id: str) -> Dict[str, Any]:
  92. url = self._url("result")
  93. r = self.session.get(url, params={"session_id": session_id}, timeout=self.timeout)
  94. r.raise_for_status()
  95. return r.json() if r.content else {}
  96. def export_plots(self, session_id: str) -> Dict[str, Any]:
  97. url = self._url("export")
  98. r = self.session.post(url, data={"session_id": session_id}, timeout=self.timeout)
  99. r.raise_for_status()
  100. return r.json() if r.content else {}
  101. def plot_raw(self, session_id: str) -> Dict[str, Any]:
  102. url = self._url("plot_raw")
  103. r = self.session.post(url, data={"session_id": session_id}, timeout=self.timeout)
  104. r.raise_for_status()
  105. return r.json() if r.content else {}
  106. def export_raw_data(self, session_id: str) -> Dict[str, Any]:
  107. return self._post_with_session_fallback("export_raw_data", session_id)
  108. def export_filter_data(
  109. self,
  110. session_id: str,
  111. center_freq: float,
  112. lower_freq: float,
  113. higher_freq: float,
  114. low_freq: float,
  115. ) -> Dict[str, Any]:
  116. return self._post_with_session_fallback(
  117. "export_filter_data",
  118. session_id,
  119. extra_form={
  120. "center_freq": center_freq,
  121. "lower_freq": lower_freq,
  122. "higher_freq": higher_freq,
  123. "low_freq": low_freq,
  124. },
  125. )
  126. def export_decdem_data(self, session_id: str) -> Dict[str, Any]:
  127. return self._post_with_session_fallback("export_decdem_data", session_id)
  128. def export_position_freq(self, session_id: str) -> Dict[str, Any]:
  129. return self._post_with_session_fallback("export_position_freq", session_id)
  130. def export_fwhm(self, session_id: str) -> Dict[str, Any]:
  131. return self._post_with_session_fallback("export_fwhm", session_id)
  132. def export_max_amplitude_freq(self, session_id: str) -> Dict[str, Any]:
  133. return self._post_with_session_fallback("export_max_amplitude_freq", session_id)
  134. def download_bytes(self, rel_or_abs_url: str) -> bytes:
  135. s = (rel_or_abs_url or "").strip()
  136. if not s:
  137. raise ValueError("download_bytes(): empty url")
  138. # Если бэкенд вернул абсолютный URL — используем как есть.
  139. if s.startswith("http://") or s.startswith("https://"):
  140. url = s
  141. else:
  142. # иначе считаем относительным к base
  143. if not s.startswith("/"):
  144. s = "/" + s
  145. url = self.base + s
  146. r = self.session.get(url, timeout=self.timeout)
  147. r.raise_for_status()
  148. return r.content