scitex 2.16.2__py3-none-any.whl → 2.17.0__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.
Files changed (85) hide show
  1. scitex/_mcp_resources/_cheatsheet.py +1 -1
  2. scitex/_mcp_resources/_modules.py +1 -1
  3. scitex/_mcp_tools/__init__.py +2 -0
  4. scitex/_mcp_tools/verify.py +256 -0
  5. scitex/cli/main.py +2 -0
  6. scitex/cli/verify.py +476 -0
  7. scitex/dev/plt/__init__.py +1 -1
  8. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +90 -0
  9. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +1571 -0
  10. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +6262 -0
  11. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +1274 -0
  12. scitex/dev/plt/data/mpl/dir_ax.txt +459 -0
  13. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  14. scitex/dev/plt/mpl/get_signatures.py +1 -1
  15. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  16. scitex/io/_load.py +8 -1
  17. scitex/io/_save.py +12 -0
  18. scitex/scholar/data/.gitkeep +0 -0
  19. scitex/scholar/data/README.md +44 -0
  20. scitex/scholar/data/bib_files/bibliography.bib +1952 -0
  21. scitex/scholar/data/bib_files/neurovista.bib +277 -0
  22. scitex/scholar/data/bib_files/neurovista_enriched.bib +441 -0
  23. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +441 -0
  24. scitex/scholar/data/bib_files/neurovista_processed.bib +338 -0
  25. scitex/scholar/data/bib_files/openaccess.bib +89 -0
  26. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +2178 -0
  27. scitex/scholar/data/bib_files/pac.bib +698 -0
  28. scitex/scholar/data/bib_files/pac_enriched.bib +1061 -0
  29. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  30. scitex/scholar/data/bib_files/pac_titles.txt +75 -0
  31. scitex/scholar/data/bib_files/paywalled.bib +98 -0
  32. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +58 -0
  33. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +87 -0
  34. scitex/scholar/data/bib_files/seizure_prediction.bib +694 -0
  35. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  36. scitex/scholar/data/bib_files/test_complete_enriched.bib +437 -0
  37. scitex/scholar/data/bib_files/test_final_enriched.bib +437 -0
  38. scitex/scholar/data/bib_files/test_seizure.bib +46 -0
  39. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  40. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  41. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  42. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  43. scitex/scholar/data/impact_factor.db +0 -0
  44. scitex/session/README.md +2 -2
  45. scitex/session/__init__.py +1 -0
  46. scitex/session/_decorator.py +57 -33
  47. scitex/session/_lifecycle/__init__.py +23 -0
  48. scitex/session/_lifecycle/_close.py +225 -0
  49. scitex/session/_lifecycle/_config.py +112 -0
  50. scitex/session/_lifecycle/_matplotlib.py +83 -0
  51. scitex/session/_lifecycle/_start.py +246 -0
  52. scitex/session/_lifecycle/_utils.py +186 -0
  53. scitex/session/_manager.py +40 -3
  54. scitex/session/template.py +1 -1
  55. scitex/template/_templates/plt.py +1 -1
  56. scitex/template/_templates/session.py +1 -1
  57. scitex/verify/README.md +312 -0
  58. scitex/verify/__init__.py +212 -0
  59. scitex/verify/_chain.py +369 -0
  60. scitex/verify/_db.py +600 -0
  61. scitex/verify/_hash.py +187 -0
  62. scitex/verify/_integration.py +127 -0
  63. scitex/verify/_rerun.py +253 -0
  64. scitex/verify/_tracker.py +330 -0
  65. scitex/verify/_visualize.py +48 -0
  66. scitex/verify/_viz/__init__.py +56 -0
  67. scitex/verify/_viz/_colors.py +84 -0
  68. scitex/verify/_viz/_format.py +302 -0
  69. scitex/verify/_viz/_json.py +192 -0
  70. scitex/verify/_viz/_mermaid.py +440 -0
  71. scitex/verify/_viz/_plotly.py +193 -0
  72. scitex/verify/_viz/_templates.py +246 -0
  73. scitex/verify/_viz/_utils.py +56 -0
  74. {scitex-2.16.2.dist-info → scitex-2.17.0.dist-info}/METADATA +1 -1
  75. {scitex-2.16.2.dist-info → scitex-2.17.0.dist-info}/RECORD +78 -29
  76. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +0 -462
  77. scitex/scholar/url_finder/.tmp/open_url/README.md +0 -223
  78. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +0 -694
  79. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +0 -1160
  80. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +0 -344
  81. scitex/scholar/url_finder/.tmp/open_url/__init__.py +0 -24
  82. scitex/session/_lifecycle.py +0 -827
  83. {scitex-2.16.2.dist-info → scitex-2.17.0.dist-info}/WHEEL +0 -0
  84. {scitex-2.16.2.dist-info → scitex-2.17.0.dist-info}/entry_points.txt +0 -0
  85. {scitex-2.16.2.dist-info → scitex-2.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-02-01 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/verify/_chain.py
4
+ """Dependency chain tracking and verification."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional, Union
12
+
13
+ from ._db import get_db
14
+ from ._hash import hash_file
15
+
16
+
17
+ class VerificationStatus(Enum):
18
+ """Verification status for a run or file."""
19
+
20
+ VERIFIED = "verified"
21
+ MISMATCH = "mismatch"
22
+ MISSING = "missing"
23
+ UNKNOWN = "unknown"
24
+
25
+
26
+ class VerificationLevel(Enum):
27
+ """Level of verification performed."""
28
+
29
+ CACHE = "cache" # Hash comparison only (fast)
30
+ RERUN = "rerun" # Full re-execution (thorough)
31
+
32
+
33
+ @dataclass
34
+ class FileVerification:
35
+ """Verification result for a single file."""
36
+
37
+ path: str
38
+ role: str
39
+ expected_hash: str
40
+ current_hash: Optional[str]
41
+ status: VerificationStatus
42
+
43
+ @property
44
+ def is_verified(self) -> bool:
45
+ return self.status == VerificationStatus.VERIFIED
46
+
47
+
48
+ @dataclass
49
+ class RunVerification:
50
+ """Verification result for a session run."""
51
+
52
+ session_id: str
53
+ script_path: Optional[str]
54
+ status: VerificationStatus
55
+ files: List[FileVerification]
56
+ combined_hash_expected: Optional[str]
57
+ combined_hash_current: Optional[str]
58
+ level: VerificationLevel = VerificationLevel.CACHE
59
+
60
+ @property
61
+ def is_verified(self) -> bool:
62
+ return self.status == VerificationStatus.VERIFIED
63
+
64
+ @property
65
+ def is_verified_from_scratch(self) -> bool:
66
+ return self.is_verified and self.level == VerificationLevel.RERUN
67
+
68
+ @property
69
+ def inputs(self) -> List[FileVerification]:
70
+ return [f for f in self.files if f.role == "input"]
71
+
72
+ @property
73
+ def outputs(self) -> List[FileVerification]:
74
+ return [f for f in self.files if f.role == "output"]
75
+
76
+ @property
77
+ def mismatched_files(self) -> List[FileVerification]:
78
+ return [f for f in self.files if f.status == VerificationStatus.MISMATCH]
79
+
80
+ @property
81
+ def missing_files(self) -> List[FileVerification]:
82
+ return [f for f in self.files if f.status == VerificationStatus.MISSING]
83
+
84
+
85
+ @dataclass
86
+ class ChainVerification:
87
+ """Verification result for a dependency chain."""
88
+
89
+ target_file: str
90
+ runs: List[RunVerification]
91
+ status: VerificationStatus
92
+
93
+ @property
94
+ def is_verified(self) -> bool:
95
+ return self.status == VerificationStatus.VERIFIED
96
+
97
+ @property
98
+ def failed_runs(self) -> List[RunVerification]:
99
+ return [r for r in self.runs if not r.is_verified]
100
+
101
+
102
+ def verify_file(
103
+ path: Union[str, Path],
104
+ expected_hash: str,
105
+ role: str = "unknown",
106
+ ) -> FileVerification:
107
+ """
108
+ Verify a single file against expected hash.
109
+
110
+ Parameters
111
+ ----------
112
+ path : str or Path
113
+ Path to the file
114
+ expected_hash : str
115
+ Expected hash value
116
+ role : str, optional
117
+ Role of the file (input, output, script)
118
+
119
+ Returns
120
+ -------
121
+ FileVerification
122
+ Verification result
123
+ """
124
+ path = Path(path)
125
+ path_str = str(path)
126
+
127
+ if not path.exists():
128
+ return FileVerification(
129
+ path=path_str,
130
+ role=role,
131
+ expected_hash=expected_hash,
132
+ current_hash=None,
133
+ status=VerificationStatus.MISSING,
134
+ )
135
+
136
+ current_hash = hash_file(path)
137
+
138
+ # Compare only the length of expected_hash
139
+ matches = current_hash[: len(expected_hash)] == expected_hash
140
+
141
+ return FileVerification(
142
+ path=path_str,
143
+ role=role,
144
+ expected_hash=expected_hash,
145
+ current_hash=current_hash,
146
+ status=VerificationStatus.VERIFIED if matches else VerificationStatus.MISMATCH,
147
+ )
148
+
149
+
150
+ def verify_run(
151
+ target: str,
152
+ propagate: bool = True,
153
+ ) -> RunVerification:
154
+ """
155
+ Verify a session run by checking all file hashes.
156
+
157
+ Parameters
158
+ ----------
159
+ target : str
160
+ Session ID, script path, or artifact path
161
+ propagate : bool
162
+ If True, mark as failed if any upstream input has failed verification
163
+
164
+ Returns
165
+ -------
166
+ RunVerification
167
+ Verification result
168
+ """
169
+ db = get_db()
170
+
171
+ # Resolve target to session_id
172
+ session_id = _resolve_target(db, target)
173
+ if not session_id:
174
+ return RunVerification(
175
+ session_id=target,
176
+ script_path=None,
177
+ status=VerificationStatus.UNKNOWN,
178
+ files=[],
179
+ combined_hash_expected=None,
180
+ combined_hash_current=None,
181
+ )
182
+
183
+ # Get run info
184
+ run_info = db.get_run(session_id)
185
+ if not run_info:
186
+ return RunVerification(
187
+ session_id=session_id,
188
+ script_path=None,
189
+ status=VerificationStatus.UNKNOWN,
190
+ files=[],
191
+ combined_hash_expected=None,
192
+ combined_hash_current=None,
193
+ )
194
+
195
+ # Get all file hashes
196
+ input_hashes = db.get_file_hashes(session_id, role="input")
197
+ output_hashes = db.get_file_hashes(session_id, role="output")
198
+
199
+ # Verify each file
200
+ file_verifications = []
201
+ upstream_failed = False
202
+
203
+ for path, expected in input_hashes.items():
204
+ fv = verify_file(path, expected, role="input")
205
+ file_verifications.append(fv)
206
+
207
+ # Check if upstream session that produced this input has failed
208
+ if propagate and not fv.is_verified:
209
+ upstream_failed = True
210
+
211
+ for path, expected in output_hashes.items():
212
+ file_verifications.append(verify_file(path, expected, role="output"))
213
+
214
+ # Verify script if present
215
+ if run_info.get("script_path") and run_info.get("script_hash"):
216
+ script_verification = verify_file(
217
+ run_info["script_path"],
218
+ run_info["script_hash"],
219
+ role="script",
220
+ )
221
+ file_verifications.append(script_verification)
222
+
223
+ # Determine overall status (upstream failure propagates)
224
+ if upstream_failed:
225
+ status = VerificationStatus.MISMATCH
226
+ elif all(f.is_verified for f in file_verifications):
227
+ status = VerificationStatus.VERIFIED
228
+ elif any(f.status == VerificationStatus.MISMATCH for f in file_verifications):
229
+ status = VerificationStatus.MISMATCH
230
+ elif any(f.status == VerificationStatus.MISSING for f in file_verifications):
231
+ status = VerificationStatus.MISSING
232
+ else:
233
+ status = VerificationStatus.UNKNOWN
234
+
235
+ return RunVerification(
236
+ session_id=session_id,
237
+ script_path=run_info.get("script_path"),
238
+ status=status,
239
+ files=file_verifications,
240
+ combined_hash_expected=run_info.get("combined_hash"),
241
+ combined_hash_current=None,
242
+ )
243
+
244
+
245
+ def _resolve_target(db, target: str) -> str | None:
246
+ """Resolve target (session_id, script path, or artifact path) to session_id."""
247
+ # Try as session_id
248
+ if db.get_run(target):
249
+ return target
250
+
251
+ # Resolve to absolute path
252
+ resolved = str(Path(target).resolve())
253
+
254
+ # Try as script path
255
+ for run in db.list_runs(limit=100):
256
+ if run.get("script_path") == resolved:
257
+ return run["session_id"]
258
+
259
+ # Try as artifact (output) path
260
+ sessions = db.find_session_by_file(resolved, role="output")
261
+ return sessions[0] if sessions else None
262
+
263
+
264
+ def verify_chain(
265
+ target: Union[str, Path],
266
+ ) -> ChainVerification:
267
+ """
268
+ Verify the dependency chain for a target file.
269
+
270
+ Traces back through all sessions that produced this file
271
+ and verifies each one.
272
+
273
+ Parameters
274
+ ----------
275
+ target : str or Path
276
+ Target file to trace
277
+
278
+ Returns
279
+ -------
280
+ ChainVerification
281
+ Verification result for the entire chain
282
+ """
283
+ db = get_db()
284
+ target = str(Path(target).resolve())
285
+
286
+ # Find session that produced this output
287
+ sessions = db.find_session_by_file(target, role="output")
288
+ if not sessions:
289
+ return ChainVerification(
290
+ target_file=target,
291
+ runs=[],
292
+ status=VerificationStatus.UNKNOWN,
293
+ )
294
+
295
+ # Get the most recent session
296
+ session_id = sessions[0]
297
+
298
+ # Build chain by following parent_session links
299
+ chain = db.get_chain(session_id)
300
+
301
+ # Verify each run in the chain
302
+ run_verifications = []
303
+ for sid in chain:
304
+ run_verifications.append(verify_run(sid))
305
+
306
+ # Determine overall status
307
+ if all(r.is_verified for r in run_verifications):
308
+ status = VerificationStatus.VERIFIED
309
+ elif any(r.status == VerificationStatus.MISMATCH for r in run_verifications):
310
+ status = VerificationStatus.MISMATCH
311
+ elif any(r.status == VerificationStatus.MISSING for r in run_verifications):
312
+ status = VerificationStatus.MISSING
313
+ else:
314
+ status = VerificationStatus.UNKNOWN
315
+
316
+ return ChainVerification(
317
+ target_file=target,
318
+ runs=run_verifications,
319
+ status=status,
320
+ )
321
+
322
+
323
+ def get_status() -> Dict[str, Any]:
324
+ """
325
+ Get verification status for all runs (like git status).
326
+
327
+ Returns
328
+ -------
329
+ dict
330
+ Summary of verification status
331
+ """
332
+ db = get_db()
333
+ runs = db.list_runs(limit=1000)
334
+
335
+ verified = []
336
+ mismatched = []
337
+ missing = []
338
+
339
+ for run in runs:
340
+ session_id = run["session_id"]
341
+ verification = verify_run(session_id)
342
+
343
+ if verification.is_verified:
344
+ verified.append(session_id)
345
+ elif verification.mismatched_files:
346
+ mismatched.append(
347
+ {
348
+ "session_id": session_id,
349
+ "files": [f.path for f in verification.mismatched_files],
350
+ }
351
+ )
352
+ elif verification.missing_files:
353
+ missing.append(
354
+ {
355
+ "session_id": session_id,
356
+ "files": [f.path for f in verification.missing_files],
357
+ }
358
+ )
359
+
360
+ return {
361
+ "verified_count": len(verified),
362
+ "mismatch_count": len(mismatched),
363
+ "missing_count": len(missing),
364
+ "mismatched": mismatched,
365
+ "missing": missing,
366
+ }
367
+
368
+
369
+ # EOF