endoreg-db 0.8.3.3__py3-none-any.whl → 0.8.6.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +23 -1
- endoreg_db/data/setup_config.yaml +38 -0
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +1 -2
- endoreg_db/management/commands/load_ai_model_data.py +18 -15
- endoreg_db/management/commands/setup_endoreg_db.py +218 -33
- endoreg_db/models/media/pdf/raw_pdf.py +241 -97
- endoreg_db/models/media/video/pipe_1.py +30 -33
- endoreg_db/models/media/video/video_file.py +300 -187
- endoreg_db/models/medical/hardware/endoscopy_processor.py +10 -1
- endoreg_db/models/metadata/model_meta_logic.py +34 -45
- endoreg_db/models/metadata/sensitive_meta_logic.py +555 -150
- endoreg_db/serializers/__init__.py +26 -55
- endoreg_db/serializers/misc/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +65 -35
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- endoreg_db/serializers/video_examination.py +198 -0
- endoreg_db/services/lookup_service.py +228 -58
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +585 -282
- endoreg_db/services/video_import.py +493 -240
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +103 -66
- endoreg_db/utils/setup_config.py +177 -0
- endoreg_db/views/__init__.py +5 -3
- endoreg_db/views/media/pdf_media.py +3 -1
- endoreg_db/views/media/video_media.py +1 -1
- endoreg_db/views/media/video_segments.py +187 -259
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/reimport.py +110 -94
- endoreg_db/views/requirement/lookup.py +171 -287
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/METADATA +1 -2
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/RECORD +38 -37
- endoreg_db/views/pdf/pdf_media.py +0 -239
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/video/video_media.py +0 -158
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,35 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lookup Service Module
|
|
3
|
+
|
|
4
|
+
This module provides server-side evaluation and lookup functionality for patient examinations.
|
|
5
|
+
It handles requirement set evaluation, finding availability, and status computation for
|
|
6
|
+
medical examination workflows.
|
|
7
|
+
|
|
8
|
+
The lookup system uses a token-based approach where client sessions are stored in Django cache,
|
|
9
|
+
allowing for efficient state management and recomputation of derived data.
|
|
10
|
+
|
|
11
|
+
Key Components:
|
|
12
|
+
- PatientExamination loading with optimized prefetching
|
|
13
|
+
- Requirement set resolution and evaluation
|
|
14
|
+
- Status computation for requirements and requirement sets
|
|
15
|
+
- Suggested actions for unsatisfied requirements
|
|
16
|
+
- Cache-based session management
|
|
17
|
+
|
|
18
|
+
Architecture:
|
|
19
|
+
1. LookupStore: Handles cache-based session storage
|
|
20
|
+
2. lookup_service: Core business logic for evaluation
|
|
21
|
+
3. LookupViewSet: Django REST API endpoints
|
|
22
|
+
"""
|
|
23
|
+
|
|
1
24
|
# services/lookup_service.py
|
|
2
25
|
from __future__ import annotations
|
|
3
|
-
|
|
26
|
+
|
|
27
|
+
from typing import Any, Dict, List
|
|
28
|
+
|
|
4
29
|
from django.db.models import Prefetch
|
|
5
|
-
|
|
30
|
+
|
|
6
31
|
from endoreg_db.models.medical.examination import ExaminationRequirementSet
|
|
32
|
+
from endoreg_db.models.medical.patient.patient_examination import PatientExamination
|
|
7
33
|
from endoreg_db.models.requirement.requirement_set import RequirementSet
|
|
34
|
+
|
|
8
35
|
from .lookup_store import LookupStore
|
|
9
36
|
|
|
10
37
|
|
|
11
38
|
def load_patient_exam_for_eval(pk: int) -> PatientExamination:
|
|
12
39
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
Load a PatientExamination with all related data needed for evaluation.
|
|
41
|
+
|
|
42
|
+
This function performs optimized database queries to fetch a PatientExamination
|
|
43
|
+
along with all related objects required for requirement evaluation, including:
|
|
44
|
+
- Patient and examination details
|
|
45
|
+
- Patient findings
|
|
46
|
+
- Examination requirement sets and their requirements
|
|
47
|
+
- Nested requirement set relationships
|
|
48
|
+
|
|
49
|
+
The query uses select_related and prefetch_related to minimize database hits
|
|
50
|
+
and ensure all data is available for evaluation without additional queries.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
pk: Primary key of the PatientExamination to load
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
PatientExamination: Fully loaded instance with all related data prefetched
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
PatientExamination.DoesNotExist: If no examination exists with the given pk
|
|
15
60
|
"""
|
|
16
61
|
return (
|
|
17
|
-
PatientExamination.objects
|
|
18
|
-
.select_related("patient", "examination")
|
|
62
|
+
PatientExamination.objects.select_related("patient", "examination")
|
|
19
63
|
.prefetch_related(
|
|
20
64
|
"patient_findings",
|
|
21
65
|
# Prefetch ERS groups on the Examination…
|
|
22
66
|
Prefetch(
|
|
23
67
|
"examination__exam_reqset_links",
|
|
24
|
-
queryset=ExaminationRequirementSet.objects.only(
|
|
68
|
+
queryset=ExaminationRequirementSet.objects.only(
|
|
69
|
+
"id", "name", "enabled_by_default"
|
|
70
|
+
),
|
|
25
71
|
),
|
|
26
72
|
# …and the RequirementSets reachable via those ERS groups.
|
|
27
73
|
Prefetch(
|
|
28
74
|
"examination__exam_reqset_links__requirement_set",
|
|
29
75
|
queryset=(
|
|
30
|
-
RequirementSet.objects
|
|
31
|
-
|
|
32
|
-
.prefetch_related(
|
|
76
|
+
RequirementSet.objects.select_related(
|
|
77
|
+
"requirement_set_type"
|
|
78
|
+
).prefetch_related(
|
|
33
79
|
"requirements",
|
|
34
80
|
"links_to_sets",
|
|
35
81
|
"links_to_sets__requirements",
|
|
@@ -44,27 +90,66 @@ def load_patient_exam_for_eval(pk: int) -> PatientExamination:
|
|
|
44
90
|
|
|
45
91
|
def requirement_sets_for_patient_exam(pe: PatientExamination) -> List[RequirementSet]:
|
|
46
92
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
93
|
+
Get all requirement sets applicable to a patient examination.
|
|
94
|
+
|
|
95
|
+
This function resolves requirement sets through the examination's requirement set links.
|
|
96
|
+
It follows the relationship: PatientExamination → Examination → ExaminationRequirementSet → RequirementSet
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
pe: PatientExamination instance to get requirement sets for
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of RequirementSet instances applicable to the examination, with related data prefetched
|
|
49
103
|
"""
|
|
50
104
|
exam = pe.examination
|
|
51
105
|
if not exam:
|
|
52
106
|
return []
|
|
53
107
|
return list(
|
|
54
|
-
RequirementSet.objects
|
|
55
|
-
.filter(reqset_exam_links__examinations=exam)
|
|
108
|
+
RequirementSet.objects.filter(reqset_exam_links__examinations=exam)
|
|
56
109
|
.select_related("requirement_set_type")
|
|
57
110
|
.prefetch_related("requirements")
|
|
58
111
|
.distinct()
|
|
59
112
|
)
|
|
60
113
|
|
|
114
|
+
|
|
61
115
|
def build_initial_lookup(pe: PatientExamination) -> Dict[str, Any]:
|
|
62
116
|
"""
|
|
63
|
-
Build the initial lookup
|
|
64
|
-
|
|
117
|
+
Build the initial lookup dictionary for a patient examination.
|
|
118
|
+
|
|
119
|
+
This function creates the base lookup data structure that will be stored in cache
|
|
120
|
+
and used by the client for requirement evaluation. It includes:
|
|
121
|
+
|
|
122
|
+
- Available findings for the examination type
|
|
123
|
+
- Required findings based on requirement defaults
|
|
124
|
+
- Requirement sets metadata
|
|
125
|
+
- Default findings and classification choices per requirement
|
|
126
|
+
- Empty placeholders for dynamic data (status, suggestions, etc.)
|
|
127
|
+
|
|
128
|
+
The returned dictionary is JSON-serializable and contains stable keys that
|
|
129
|
+
won't change between versions.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
pe: PatientExamination instance to build lookup for
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary containing initial lookup data with the following keys:
|
|
136
|
+
- patient_examination_id: ID of the patient examination
|
|
137
|
+
- requirement_sets: List of available requirement sets with metadata
|
|
138
|
+
- availableFindings: List of finding IDs available for the examination
|
|
139
|
+
- requiredFindings: List of finding IDs that are required by defaults
|
|
140
|
+
- requirementDefaults: Default findings per requirement
|
|
141
|
+
- classificationChoices: Available classification choices per requirement
|
|
142
|
+
- requirementsBySet: Empty dict (populated on selection)
|
|
143
|
+
- requirementStatus: Empty dict (computed on evaluation)
|
|
144
|
+
- requirementSetStatus: Empty dict (computed on evaluation)
|
|
145
|
+
- suggestedActions: Empty dict (computed on evaluation)
|
|
65
146
|
"""
|
|
66
147
|
# Available + required findings
|
|
67
|
-
available_findings =
|
|
148
|
+
available_findings = (
|
|
149
|
+
[f.id for f in pe.examination.get_available_findings()]
|
|
150
|
+
if pe.examination
|
|
151
|
+
else []
|
|
152
|
+
)
|
|
68
153
|
required_findings: List[int] = [] # fill by scanning requirements below
|
|
69
154
|
|
|
70
155
|
# Requirement sets: ids + meta
|
|
@@ -90,9 +175,13 @@ def build_initial_lookup(pe: PatientExamination) -> Dict[str, Any]:
|
|
|
90
175
|
choices = getattr(req, "classification_choices", lambda pe: [])(pe)
|
|
91
176
|
if defaults:
|
|
92
177
|
req_defaults[str(req.id)] = defaults # list of {finding_id, payload...}
|
|
93
|
-
required_findings.extend(
|
|
178
|
+
required_findings.extend(
|
|
179
|
+
[d.get("finding_id") for d in defaults if "finding_id" in d]
|
|
180
|
+
)
|
|
94
181
|
if choices:
|
|
95
|
-
cls_choices[str(req.id)] =
|
|
182
|
+
cls_choices[str(req.id)] = (
|
|
183
|
+
choices # list of {classification_id, label, ...}
|
|
184
|
+
)
|
|
96
185
|
|
|
97
186
|
# De-dup required
|
|
98
187
|
required_findings = sorted(set(required_findings))
|
|
@@ -112,62 +201,129 @@ def build_initial_lookup(pe: PatientExamination) -> Dict[str, Any]:
|
|
|
112
201
|
# You can add "selectedRequirementSetIds" as the user makes choices
|
|
113
202
|
}
|
|
114
203
|
|
|
204
|
+
|
|
115
205
|
def create_lookup_token_for_pe(pe_id: int) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Create a lookup token for a patient examination.
|
|
208
|
+
|
|
209
|
+
This function initializes a new lookup session for the given patient examination
|
|
210
|
+
by building the initial lookup data and storing it in the cache via LookupStore.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
pe_id: Primary key of the PatientExamination
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
String token that can be used to access the lookup session
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
PatientExamination.DoesNotExist: If examination doesn't exist
|
|
220
|
+
Exception: For any other errors during initialization
|
|
221
|
+
"""
|
|
116
222
|
pe = load_patient_exam_for_eval(pe_id)
|
|
117
223
|
token = LookupStore().init(build_initial_lookup(pe))
|
|
118
224
|
return token
|
|
119
225
|
|
|
226
|
+
|
|
120
227
|
def recompute_lookup(token: str) -> Dict[str, Any]:
|
|
228
|
+
"""
|
|
229
|
+
Recompute derived lookup data based on current patient examination state and user selections.
|
|
230
|
+
|
|
231
|
+
This function performs the core evaluation logic for the lookup system. It:
|
|
232
|
+
|
|
233
|
+
1. Validates and recovers corrupted lookup data
|
|
234
|
+
2. Loads the current PatientExamination state from database
|
|
235
|
+
3. Evaluates requirements against the current examination state
|
|
236
|
+
4. Computes status for individual requirements and requirement sets
|
|
237
|
+
5. Generates suggested actions for unsatisfied requirements
|
|
238
|
+
6. Updates the cache with new derived data (idempotent)
|
|
239
|
+
|
|
240
|
+
The function includes reentrancy protection to prevent concurrent recomputation
|
|
241
|
+
of the same token.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
token: Lookup session token
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Dictionary of updates containing:
|
|
248
|
+
- requirementsBySet: Requirements grouped by selected requirement sets
|
|
249
|
+
- requirementStatus: Boolean status for each requirement
|
|
250
|
+
- requirementSetStatus: Boolean status for each requirement set
|
|
251
|
+
- requirementDefaults: Default findings per requirement
|
|
252
|
+
- classificationChoices: Available choices per requirement
|
|
253
|
+
- suggestedActions: UI actions to satisfy unsatisfied requirements
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ValueError: If lookup data is invalid or patient examination not found
|
|
257
|
+
"""
|
|
121
258
|
import logging
|
|
259
|
+
|
|
122
260
|
logger = logging.getLogger(__name__)
|
|
123
|
-
|
|
261
|
+
|
|
124
262
|
store = LookupStore(token=token)
|
|
125
|
-
|
|
263
|
+
|
|
126
264
|
# Simple reentrancy guard using data
|
|
127
265
|
data = store.get_all()
|
|
128
|
-
if data.get(
|
|
266
|
+
if data.get("_recomputing"):
|
|
129
267
|
logger.warning(f"Recompute already in progress for token {token}, skipping")
|
|
130
268
|
return {}
|
|
131
|
-
|
|
132
|
-
store.set(
|
|
133
|
-
|
|
269
|
+
|
|
270
|
+
store.set("_recomputing", True)
|
|
271
|
+
|
|
134
272
|
try:
|
|
135
273
|
# First validate and attempt to recover corrupted data
|
|
136
274
|
validated_data = store.validate_and_recover_data(token)
|
|
137
275
|
if validated_data is None:
|
|
138
276
|
logger.error(f"No lookup data found for token {token}")
|
|
139
277
|
raise ValueError(f"No lookup data found for token {token}")
|
|
140
|
-
|
|
278
|
+
|
|
141
279
|
data = validated_data
|
|
142
|
-
logger.debug(
|
|
143
|
-
|
|
280
|
+
logger.debug(
|
|
281
|
+
f"Recomputing lookup for token {token}, data keys: {list(data.keys())}"
|
|
282
|
+
)
|
|
283
|
+
|
|
144
284
|
# Check if required data exists
|
|
145
285
|
if "patient_examination_id" not in data:
|
|
146
|
-
logger.error(
|
|
147
|
-
|
|
148
|
-
|
|
286
|
+
logger.error(
|
|
287
|
+
f"Invalid lookup data for token {token}: missing patient_examination_id. Data: {data}"
|
|
288
|
+
)
|
|
289
|
+
raise ValueError(
|
|
290
|
+
f"Invalid lookup data for token {token}: missing patient_examination_id"
|
|
291
|
+
)
|
|
292
|
+
|
|
149
293
|
if not data.get("patient_examination_id"):
|
|
150
|
-
logger.error(
|
|
151
|
-
|
|
294
|
+
logger.error(
|
|
295
|
+
f"Invalid lookup data for token {token}: patient_examination_id is empty. Data: {data}"
|
|
296
|
+
)
|
|
297
|
+
raise ValueError(
|
|
298
|
+
f"Invalid lookup data for token {token}: patient_examination_id is empty"
|
|
299
|
+
)
|
|
152
300
|
|
|
153
301
|
pe_id = data["patient_examination_id"]
|
|
154
302
|
logger.debug(f"Loading patient examination {pe_id} for token {token}")
|
|
155
|
-
|
|
303
|
+
|
|
156
304
|
try:
|
|
157
305
|
pe = load_patient_exam_for_eval(pe_id)
|
|
158
306
|
except Exception as e:
|
|
159
|
-
logger.error(
|
|
307
|
+
logger.error(
|
|
308
|
+
f"Failed to load patient examination {pe_id} for token {token}: {e}"
|
|
309
|
+
)
|
|
160
310
|
raise ValueError(f"Failed to load patient examination {pe_id}: {e}")
|
|
161
311
|
|
|
162
312
|
selected_rs_ids: List[int] = data.get("selectedRequirementSetIds", [])
|
|
163
|
-
logger.debug(
|
|
164
|
-
|
|
165
|
-
|
|
313
|
+
logger.debug(
|
|
314
|
+
f"Selected requirement set IDs for token {token}: {selected_rs_ids}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
rs_objs = [
|
|
318
|
+
rs
|
|
319
|
+
for rs in requirement_sets_for_patient_exam(pe)
|
|
320
|
+
if rs.id in selected_rs_ids
|
|
321
|
+
]
|
|
166
322
|
logger.debug(f"Found {len(rs_objs)} requirement set objects for token {token}")
|
|
167
323
|
|
|
168
324
|
# 1) requirements grouped by set (already prefetched in load func)
|
|
169
325
|
requirements_by_set = {
|
|
170
|
-
rs.id: [
|
|
326
|
+
rs.id: [{"id": r.id, "name": r.name} for r in rs.requirements.all()]
|
|
171
327
|
for rs in rs_objs
|
|
172
328
|
}
|
|
173
329
|
|
|
@@ -180,7 +336,9 @@ def recompute_lookup(token: str) -> Dict[str, Any]:
|
|
|
180
336
|
ok = bool(r.evaluate(pe, mode="strict")) # or "loose" if you prefer
|
|
181
337
|
requirement_status[str(r.id)] = ok
|
|
182
338
|
req_results.append(ok)
|
|
183
|
-
set_status[str(rs.id)] =
|
|
339
|
+
set_status[str(rs.id)] = (
|
|
340
|
+
rs.eval_function(req_results) if rs.eval_function else all(req_results)
|
|
341
|
+
)
|
|
184
342
|
|
|
185
343
|
# 3) suggestions per requirement (defaults + classification choices you already expose)
|
|
186
344
|
suggested_actions: Dict[str, List[Dict[str, Any]]] = {}
|
|
@@ -189,8 +347,12 @@ def recompute_lookup(token: str) -> Dict[str, Any]:
|
|
|
189
347
|
|
|
190
348
|
for rs in rs_objs:
|
|
191
349
|
for r in rs.requirements.all():
|
|
192
|
-
defaults = getattr(r, "default_findings", lambda pe: [])(
|
|
193
|
-
|
|
350
|
+
defaults = getattr(r, "default_findings", lambda pe: [])(
|
|
351
|
+
pe
|
|
352
|
+
) # [{finding_id, payload...}]
|
|
353
|
+
choices = getattr(r, "classification_choices", lambda pe: [])(
|
|
354
|
+
pe
|
|
355
|
+
) # [{classification_id, label,...}]
|
|
194
356
|
if defaults:
|
|
195
357
|
req_defaults[str(r.id)] = defaults
|
|
196
358
|
if choices:
|
|
@@ -200,15 +362,19 @@ def recompute_lookup(token: str) -> Dict[str, Any]:
|
|
|
200
362
|
# turn default proposals into explicit UI actions
|
|
201
363
|
acts = []
|
|
202
364
|
for d in defaults or []:
|
|
203
|
-
acts.append(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
365
|
+
acts.append(
|
|
366
|
+
{
|
|
367
|
+
"type": "add_finding",
|
|
368
|
+
"finding_id": d.get("finding_id"),
|
|
369
|
+
"classification_ids": d.get("classification_ids") or [],
|
|
370
|
+
"note": "default",
|
|
371
|
+
}
|
|
372
|
+
)
|
|
209
373
|
# If r expects patient edits, add an edit action hint
|
|
210
374
|
if "PatientExamination" in [m.__name__ for m in r.expected_models]:
|
|
211
|
-
acts.append(
|
|
375
|
+
acts.append(
|
|
376
|
+
{"type": "edit_patient", "fields": ["gender", "dob"]}
|
|
377
|
+
) # example
|
|
212
378
|
if acts:
|
|
213
379
|
suggested_actions[str(r.id)] = acts
|
|
214
380
|
|
|
@@ -220,22 +386,26 @@ def recompute_lookup(token: str) -> Dict[str, Any]:
|
|
|
220
386
|
"requirementsBySet": requirements_by_set,
|
|
221
387
|
"requirementStatus": requirement_status,
|
|
222
388
|
"requirementSetStatus": set_status,
|
|
223
|
-
"requirementDefaults": req_defaults,
|
|
224
|
-
"classificationChoices": cls_choices,
|
|
225
|
-
"suggestedActions": suggested_actions,
|
|
389
|
+
"requirementDefaults": req_defaults, # keep your existing key
|
|
390
|
+
"classificationChoices": cls_choices, # keep your existing key
|
|
391
|
+
"suggestedActions": suggested_actions, # new
|
|
226
392
|
}
|
|
227
|
-
|
|
228
|
-
logger.debug(
|
|
229
|
-
|
|
393
|
+
|
|
394
|
+
logger.debug(
|
|
395
|
+
f"Updating store for token {token} with {len(updates)} update keys"
|
|
396
|
+
)
|
|
397
|
+
|
|
230
398
|
# Only write if changed (idempotent)
|
|
231
399
|
prev_derived = store.get_many(list(updates.keys()))
|
|
232
400
|
if prev_derived != updates:
|
|
233
401
|
store.set_many(updates) # <-- does NOT call recompute
|
|
234
402
|
logger.debug(f"Derived data changed, updated store for token {token}")
|
|
235
403
|
else:
|
|
236
|
-
logger.debug(
|
|
237
|
-
|
|
404
|
+
logger.debug(
|
|
405
|
+
f"Derived data unchanged, skipping store update for token {token}"
|
|
406
|
+
)
|
|
407
|
+
|
|
238
408
|
store.mark_recompute_done()
|
|
239
409
|
return updates
|
|
240
410
|
finally:
|
|
241
|
-
store.set(
|
|
411
|
+
store.set("_recomputing", False)
|