man1lab 1.2.2__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 (286) hide show
  1. adapters/__init__.py +4 -0
  2. adapters/config_parser_settings.py +18 -0
  3. adapters/docling_parser.py +88 -0
  4. adapters/factory.py +31 -0
  5. adapters/output_export.py +21 -0
  6. adapters/pymupdf_parser.py +15 -0
  7. agents/__init__.py +8 -0
  8. agents/analysis_context.py +147 -0
  9. agents/analysis_snapshot.py +20 -0
  10. agents/coder.py +743 -0
  11. agents/coder_quality.py +554 -0
  12. agents/planner.py +46 -0
  13. agents/reader.py +66 -0
  14. agents/reporter.py +43 -0
  15. agents/reviewer.py +48 -0
  16. agents/runner.py +25 -0
  17. application/__init__.py +13 -0
  18. application/facade.py +456 -0
  19. application/lifecycle/__init__.py +19 -0
  20. application/lifecycle/clean.py +129 -0
  21. application/lifecycle/common.py +110 -0
  22. application/lifecycle/doctor.py +213 -0
  23. application/lifecycle/init.py +123 -0
  24. application/lifecycle/init_wizard.py +111 -0
  25. application/lifecycle/llm_doctor.py +122 -0
  26. application/version.py +3 -0
  27. config.py +49 -0
  28. configuration/__init__.py +16 -0
  29. configuration/bootstrap.py +25 -0
  30. configuration/hydra_provider.py +107 -0
  31. configuration/legacy_provider.py +71 -0
  32. configuration/models.py +83 -0
  33. configuration/paths.py +47 -0
  34. configuration/provider.py +27 -0
  35. discovery/__init__.py +0 -0
  36. discovery/empty.py +66 -0
  37. discovery/workflow.py +374 -0
  38. execution/__init__.py +3 -0
  39. execution/execution_planner.py +31 -0
  40. execution_planning/__init__.py +6 -0
  41. execution_planning/builder.py +151 -0
  42. execution_planning/workflow.py +188 -0
  43. interfaces/__init__.py +6 -0
  44. interfaces/api/__init__.py +3 -0
  45. interfaces/cli/__init__.py +1 -0
  46. interfaces/cli/app.py +62 -0
  47. interfaces/cli/commands/__init__.py +1 -0
  48. interfaces/cli/commands/analyze.py +36 -0
  49. interfaces/cli/commands/clean.py +88 -0
  50. interfaces/cli/commands/config.py +18 -0
  51. interfaces/cli/commands/discover.py +36 -0
  52. interfaces/cli/commands/doctor.py +34 -0
  53. interfaces/cli/commands/execute.py +36 -0
  54. interfaces/cli/commands/init.py +113 -0
  55. interfaces/cli/commands/model.py +292 -0
  56. interfaces/cli/commands/plan.py +36 -0
  57. interfaces/cli/commands/reproduce.py +28 -0
  58. interfaces/cli/commands/version.py +12 -0
  59. interfaces/cli/common.py +60 -0
  60. interfaces/mcp/__init__.py +3 -0
  61. interfaces/sdk/__init__.py +12 -0
  62. interfaces/sdk/client.py +114 -0
  63. llm/__init__.py +15 -0
  64. llm/anthropic_provider.py +29 -0
  65. llm/coder_mock_provider.py +180 -0
  66. llm/compat.py +34 -0
  67. llm/factory.py +34 -0
  68. llm/mock_provider.py +203 -0
  69. llm/openai_provider.py +30 -0
  70. llm/provider.py +11 -0
  71. llm/response_parser.py +39 -0
  72. man1lab/__init__.py +12 -0
  73. man1lab/__main__.py +6 -0
  74. man1lab-1.2.2.data/data/share/man1lab/.env.example +7 -0
  75. man1lab-1.2.2.data/data/share/man1lab/conf/analysis/default.yaml +1 -0
  76. man1lab-1.2.2.data/data/share/man1lab/conf/coder/default.yaml +1 -0
  77. man1lab-1.2.2.data/data/share/man1lab/conf/config.yaml +21 -0
  78. man1lab-1.2.2.data/data/share/man1lab/conf/discovery/default.yaml +1 -0
  79. man1lab-1.2.2.data/data/share/man1lab/conf/execution_planning/default.yaml +1 -0
  80. man1lab-1.2.2.data/data/share/man1lab/conf/llm/default.yaml +32 -0
  81. man1lab-1.2.2.data/data/share/man1lab/conf/logging/default.yaml +2 -0
  82. man1lab-1.2.2.data/data/share/man1lab/conf/parser/default.yaml +2 -0
  83. man1lab-1.2.2.data/data/share/man1lab/conf/planner/default.yaml +1 -0
  84. man1lab-1.2.2.data/data/share/man1lab/conf/reporter/default.yaml +1 -0
  85. man1lab-1.2.2.data/data/share/man1lab/conf/reviewer/default.yaml +1 -0
  86. man1lab-1.2.2.data/data/share/man1lab/conf/runner/default.yaml +1 -0
  87. man1lab-1.2.2.data/data/share/man1lab/conf/tracking/default.yaml +4 -0
  88. man1lab-1.2.2.data/data/share/man1lab/conf/workflow/default.yaml +1 -0
  89. man1lab-1.2.2.data/data/share/man1lab/prompts/coder/config.md +9 -0
  90. man1lab-1.2.2.data/data/share/man1lab/prompts/coder/dependencies.md +9 -0
  91. man1lab-1.2.2.data/data/share/man1lab/prompts/coder/script.md +12 -0
  92. man1lab-1.2.2.data/data/share/man1lab/prompts/coder/source.md +10 -0
  93. man1lab-1.2.2.data/data/share/man1lab/prompts/coder/system.md +3 -0
  94. man1lab-1.2.2.data/data/share/man1lab/prompts/patch_planner/examples.md +19 -0
  95. man1lab-1.2.2.data/data/share/man1lab/prompts/patch_planner/extraction.md +8 -0
  96. man1lab-1.2.2.data/data/share/man1lab/prompts/patch_planner/schema.md +12 -0
  97. man1lab-1.2.2.data/data/share/man1lab/prompts/patch_planner/system.md +10 -0
  98. man1lab-1.2.2.data/data/share/man1lab/prompts/planner/examples.md +1 -0
  99. man1lab-1.2.2.data/data/share/man1lab/prompts/planner/extraction.md +3 -0
  100. man1lab-1.2.2.data/data/share/man1lab/prompts/planner/schema.md +16 -0
  101. man1lab-1.2.2.data/data/share/man1lab/prompts/planner/system.md +10 -0
  102. man1lab-1.2.2.data/data/share/man1lab/prompts/reader/examples.md +81 -0
  103. man1lab-1.2.2.data/data/share/man1lab/prompts/reader/extraction.md +61 -0
  104. man1lab-1.2.2.data/data/share/man1lab/prompts/reader/output.md +14 -0
  105. man1lab-1.2.2.data/data/share/man1lab/prompts/reader/schema.md +77 -0
  106. man1lab-1.2.2.data/data/share/man1lab/prompts/reader/system.md +36 -0
  107. man1lab-1.2.2.data/data/share/man1lab/prompts/reviewer/examples.md +30 -0
  108. man1lab-1.2.2.data/data/share/man1lab/prompts/reviewer/extraction.md +9 -0
  109. man1lab-1.2.2.data/data/share/man1lab/prompts/reviewer/schema.md +13 -0
  110. man1lab-1.2.2.data/data/share/man1lab/prompts/reviewer/system.md +11 -0
  111. man1lab-1.2.2.dist-info/METADATA +240 -0
  112. man1lab-1.2.2.dist-info/RECORD +286 -0
  113. man1lab-1.2.2.dist-info/WHEEL +5 -0
  114. man1lab-1.2.2.dist-info/entry_points.txt +2 -0
  115. man1lab-1.2.2.dist-info/licenses/LICENSE +21 -0
  116. man1lab-1.2.2.dist-info/top_level.txt +23 -0
  117. models/__init__.py +68 -0
  118. models/execution.py +14 -0
  119. models/execution_plan.py +11 -0
  120. models/execution_planning_runtime.py +385 -0
  121. models/execution_strategy.py +632 -0
  122. models/paper.py +19 -0
  123. models/paper_reproduction_analysis.py +173 -0
  124. models/report.py +41 -0
  125. models/research_resource_discovery.py +549 -0
  126. models/review.py +11 -0
  127. models/review_report.py +12 -0
  128. models/routing.py +15 -0
  129. models/task.py +17 -0
  130. models/verification.py +23 -0
  131. models/workspace.py +10 -0
  132. planning/__init__.py +3 -0
  133. planning/patch_planner.py +38 -0
  134. ports/__init__.py +12 -0
  135. ports/adaptation_provider.py +19 -0
  136. ports/collection_provider.py +21 -0
  137. ports/document_parser.py +11 -0
  138. ports/evidence_provider.py +26 -0
  139. ports/generation_provider.py +19 -0
  140. ports/output_format.py +9 -0
  141. ports/parsed_document.py +21 -0
  142. ports/parser_settings.py +19 -0
  143. ports/ranking_provider.py +29 -0
  144. ports/resource_binding_provider.py +19 -0
  145. ports/reuse_provider.py +19 -0
  146. ports/risk_provider.py +19 -0
  147. ports/strategy_provider.py +18 -0
  148. ports/verification_provider.py +32 -0
  149. prompt/__init__.py +5 -0
  150. prompt/builder.py +33 -0
  151. prompt/exceptions.py +2 -0
  152. prompt/loader.py +25 -0
  153. prompts/coder/config.md +9 -0
  154. prompts/coder/dependencies.md +9 -0
  155. prompts/coder/script.md +12 -0
  156. prompts/coder/source.md +10 -0
  157. prompts/coder/system.md +3 -0
  158. prompts/patch_planner/examples.md +19 -0
  159. prompts/patch_planner/extraction.md +8 -0
  160. prompts/patch_planner/schema.md +12 -0
  161. prompts/patch_planner/system.md +10 -0
  162. prompts/planner/examples.md +1 -0
  163. prompts/planner/extraction.md +3 -0
  164. prompts/planner/schema.md +16 -0
  165. prompts/planner/system.md +10 -0
  166. prompts/reader/examples.md +81 -0
  167. prompts/reader/extraction.md +61 -0
  168. prompts/reader/output.md +14 -0
  169. prompts/reader/schema.md +77 -0
  170. prompts/reader/system.md +36 -0
  171. prompts/reviewer/examples.md +30 -0
  172. prompts/reviewer/extraction.md +9 -0
  173. prompts/reviewer/schema.md +13 -0
  174. prompts/reviewer/system.md +11 -0
  175. providers/__init__.py +24 -0
  176. providers/embedded/__init__.py +0 -0
  177. providers/embedded/adaptation.py +69 -0
  178. providers/embedded/decision_foundation/__init__.py +49 -0
  179. providers/embedded/decision_foundation/adaptation_decision.py +385 -0
  180. providers/embedded/decision_foundation/binding_decision.py +191 -0
  181. providers/embedded/decision_foundation/common.py +39 -0
  182. providers/embedded/decision_foundation/dimensions.py +87 -0
  183. providers/embedded/decision_foundation/facts.py +199 -0
  184. providers/embedded/decision_foundation/generation_decision.py +389 -0
  185. providers/embedded/decision_foundation/reuse_decision.py +321 -0
  186. providers/embedded/decision_foundation/risk_decision.py +545 -0
  187. providers/embedded/decision_foundation/strategy_decision.py +161 -0
  188. providers/embedded/embedded_evidence_provider.py +133 -0
  189. providers/embedded/embedded_ranking_provider.py +142 -0
  190. providers/embedded/embedded_resource_provider.py +379 -0
  191. providers/embedded/embedded_verification_provider.py +180 -0
  192. providers/embedded/generation.py +68 -0
  193. providers/embedded/resource_binding.py +58 -0
  194. providers/embedded/reuse.py +65 -0
  195. providers/embedded/risk.py +83 -0
  196. providers/embedded/strategy.py +58 -0
  197. providers/github/__init__.py +50 -0
  198. providers/github/auth.py +67 -0
  199. providers/github/client.py +162 -0
  200. providers/github/collection.py +207 -0
  201. providers/github/evidence.py +165 -0
  202. providers/github/exceptions.py +35 -0
  203. providers/github/mapper.py +212 -0
  204. providers/github/models.py +126 -0
  205. providers/github/ranking.py +373 -0
  206. providers/github/verification.py +473 -0
  207. providers/llm/__init__.py +35 -0
  208. providers/llm/anthropic_provider.py +156 -0
  209. providers/llm/base.py +45 -0
  210. providers/llm/deepseek_provider.py +48 -0
  211. providers/llm/errors.py +19 -0
  212. providers/llm/manager.py +305 -0
  213. providers/llm/model_management.py +208 -0
  214. providers/llm/models.py +65 -0
  215. providers/llm/openai_provider.py +93 -0
  216. providers/llm/persistence.py +207 -0
  217. providers/llm/profiles.py +250 -0
  218. providers/llm/provider_registry.py +43 -0
  219. providers/llm/registry.py +132 -0
  220. providers/noop/__init__.py +0 -0
  221. providers/noop/adaptation.py +19 -0
  222. providers/noop/collection.py +8 -0
  223. providers/noop/evidence.py +3 -0
  224. providers/noop/generation.py +20 -0
  225. providers/noop/noop_evidence_provider.py +15 -0
  226. providers/noop/noop_ranking_provider.py +17 -0
  227. providers/noop/noop_verification_provider.py +15 -0
  228. providers/noop/ranking.py +3 -0
  229. providers/noop/resource_binding.py +17 -0
  230. providers/noop/reuse.py +18 -0
  231. providers/noop/risk.py +21 -0
  232. providers/noop/strategy.py +16 -0
  233. providers/noop/verification.py +3 -0
  234. routing/__init__.py +3 -0
  235. routing/task_router.py +128 -0
  236. services/__init__.py +29 -0
  237. services/discovery/__init__.py +0 -0
  238. services/discovery/candidate_merge.py +112 -0
  239. services/discovery/collection_service.py +86 -0
  240. services/discovery/evidence_merge.py +64 -0
  241. services/discovery/evidence_service.py +53 -0
  242. services/discovery/ranking_merge.py +88 -0
  243. services/discovery/ranking_service.py +53 -0
  244. services/discovery/verification_merge.py +121 -0
  245. services/discovery/verification_service.py +51 -0
  246. services/environment_service.py +179 -0
  247. services/exceptions.py +30 -0
  248. services/execution_planning/__init__.py +17 -0
  249. services/execution_planning/adaptation_merge.py +24 -0
  250. services/execution_planning/adaptation_service.py +43 -0
  251. services/execution_planning/generation_merge.py +24 -0
  252. services/execution_planning/generation_service.py +48 -0
  253. services/execution_planning/protocols.py +69 -0
  254. services/execution_planning/resource_binding_merge.py +34 -0
  255. services/execution_planning/resource_binding_service.py +43 -0
  256. services/execution_planning/reuse_merge.py +22 -0
  257. services/execution_planning/reuse_service.py +42 -0
  258. services/execution_planning/risk_merge.py +22 -0
  259. services/execution_planning/risk_service.py +45 -0
  260. services/execution_planning/strategy_merge.py +20 -0
  261. services/execution_planning/strategy_service.py +40 -0
  262. services/execution_service.py +119 -0
  263. services/pdf_service.py +65 -0
  264. services/verification_service.py +223 -0
  265. tracking/__init__.py +17 -0
  266. tracking/bootstrap.py +28 -0
  267. tracking/mlflow_tracker.py +67 -0
  268. tracking/noop_tracker.py +41 -0
  269. tracking/protocol.py +41 -0
  270. tracking/provider.py +19 -0
  271. tracking/workflow.py +85 -0
  272. validation/__init__.py +61 -0
  273. validation/analysis_builder.py +16 -0
  274. validation/exceptions.py +26 -0
  275. validation/execution_strategy.py +1252 -0
  276. validation/paper.py +79 -0
  277. validation/paper_reproduction_analysis.py +582 -0
  278. validation/patch.py +71 -0
  279. validation/research_resource_discovery.py +1270 -0
  280. validation/review.py +63 -0
  281. validation/task.py +103 -0
  282. workflow/__init__.py +4 -0
  283. workflow/orchestrator.py +176 -0
  284. workflow/pipeline.py +32 -0
  285. workspace/__init__.py +3 -0
  286. workspace/manager.py +177 -0
adapters/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from adapters.config_parser_settings import ConfigParserSettingsProvider
2
+ from adapters.factory import build_document_parser
3
+
4
+ __all__ = ["ConfigParserSettingsProvider", "build_document_parser"]
@@ -0,0 +1,18 @@
1
+ from configuration.provider import SettingsProvider, get_settings_provider
2
+ from ports.output_format import OutputFormat
3
+ from ports.parser_settings import ParserSettings, ParserSettingsProvider
4
+
5
+
6
+ class ConfigParserSettingsProvider:
7
+ """Read parser settings from the application settings provider."""
8
+
9
+ def __init__(self, settings_provider: SettingsProvider | None = None) -> None:
10
+ self._settings_provider = settings_provider or get_settings_provider()
11
+
12
+ def get_parser_settings(self) -> ParserSettings:
13
+ settings = self._settings_provider.get_settings()
14
+ return ParserSettings(
15
+ backend=settings.parser.backend.strip().lower(),
16
+ output_format=OutputFormat.MARKDOWN,
17
+ max_paper_text_chars=settings.parser.max_paper_text_chars,
18
+ )
@@ -0,0 +1,88 @@
1
+ import logging
2
+ import time
3
+ from pathlib import Path
4
+
5
+ from docling.backend.pypdfium2_backend import PyPdfiumDocumentBackend
6
+ from docling.datamodel.base_models import InputFormat
7
+ from docling.datamodel.pipeline_options import PdfPipelineOptions
8
+ from docling.document_converter import DocumentConverter, PdfFormatOption
9
+
10
+ from adapters.output_export import export_docling_document
11
+ from ports.output_format import OutputFormat
12
+ from ports.parsed_document import ParsedDocument
13
+ from services.exceptions import (
14
+ PDFEmptyError,
15
+ PDFEncryptedError,
16
+ PDFExtractionError,
17
+ PDFNotFoundError,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class DoclingParser:
24
+ """Document parser backed by Docling structured document export."""
25
+
26
+ def __init__(
27
+ self,
28
+ converter: DocumentConverter | None = None,
29
+ *,
30
+ output_format: OutputFormat = OutputFormat.MARKDOWN,
31
+ max_paper_text_chars: int = 80_000,
32
+ ) -> None:
33
+ self._converter = converter or _build_converter()
34
+ self._output_format = output_format
35
+ self._max_paper_text_chars = max_paper_text_chars
36
+
37
+ def parse(self, paper_path: Path) -> ParsedDocument:
38
+ if not paper_path.exists():
39
+ raise PDFNotFoundError(f"PDF not found: {paper_path}")
40
+
41
+ start = time.perf_counter()
42
+ try:
43
+ result = self._converter.convert(str(paper_path))
44
+ except Exception as exc:
45
+ message = str(exc).lower()
46
+ if "encrypt" in message or "password" in message:
47
+ raise PDFEncryptedError(
48
+ f"PDF is encrypted and cannot be read: {paper_path}"
49
+ ) from exc
50
+ raise PDFExtractionError(
51
+ f"Failed to parse PDF with Docling: {paper_path}"
52
+ ) from exc
53
+
54
+ markdown = export_docling_document(
55
+ result.document,
56
+ self._output_format,
57
+ )
58
+ if not markdown:
59
+ raise PDFEmptyError(f"PDF contains no extractable text: {paper_path}")
60
+
61
+ if self._max_paper_text_chars and len(markdown) > self._max_paper_text_chars:
62
+ markdown = markdown[: self._max_paper_text_chars]
63
+
64
+ duration = time.perf_counter() - start
65
+ logger.info(
66
+ "Docling parse complete: chars=%d duration=%.3fs file=%s status=%s format=%s",
67
+ len(markdown),
68
+ duration,
69
+ paper_path.name,
70
+ getattr(result, "status", "unknown"),
71
+ self._output_format.value,
72
+ )
73
+ return ParsedDocument(markdown=markdown)
74
+
75
+
76
+ def _build_converter() -> DocumentConverter:
77
+ pipeline_options = PdfPipelineOptions(
78
+ do_ocr=False,
79
+ do_table_structure=True,
80
+ )
81
+ return DocumentConverter(
82
+ format_options={
83
+ InputFormat.PDF: PdfFormatOption(
84
+ pipeline_options=pipeline_options,
85
+ backend=PyPdfiumDocumentBackend,
86
+ )
87
+ }
88
+ )
adapters/factory.py ADDED
@@ -0,0 +1,31 @@
1
+ from adapters.config_parser_settings import ConfigParserSettingsProvider
2
+ from adapters.docling_parser import DoclingParser
3
+ from adapters.pymupdf_parser import PyMuPDFParser
4
+ from ports.document_parser import DocumentParser
5
+ from ports.parser_settings import ParserSettings, ParserSettingsProvider
6
+
7
+ _SUPPORTED_BACKENDS = frozenset({"docling", "pymupdf"})
8
+
9
+
10
+ def build_document_parser(
11
+ settings: ParserSettings | None = None,
12
+ settings_provider: ParserSettingsProvider | None = None,
13
+ ) -> DocumentParser:
14
+ """Construct a document parser from explicit or provider-supplied settings."""
15
+ if settings is None:
16
+ provider = settings_provider or ConfigParserSettingsProvider()
17
+ settings = provider.get_parser_settings()
18
+
19
+ backend = settings.backend.strip().lower()
20
+ if backend not in _SUPPORTED_BACKENDS:
21
+ supported = ", ".join(sorted(_SUPPORTED_BACKENDS))
22
+ raise ValueError(
23
+ f"Unsupported parser backend={settings.backend!r}. "
24
+ f"Expected one of: {supported}"
25
+ )
26
+ if backend == "pymupdf":
27
+ return PyMuPDFParser()
28
+ return DoclingParser(
29
+ output_format=settings.output_format,
30
+ max_paper_text_chars=settings.max_paper_text_chars,
31
+ )
@@ -0,0 +1,21 @@
1
+ from typing import Any
2
+
3
+ from ports.output_format import OutputFormat
4
+
5
+
6
+ def export_docling_document(
7
+ document: Any,
8
+ output_format: OutputFormat,
9
+ ) -> str:
10
+ """Serialize a Docling document using the configured output format."""
11
+ if output_format is OutputFormat.MARKDOWN:
12
+ return document.export_to_markdown().strip()
13
+ if output_format is OutputFormat.JSON:
14
+ raise NotImplementedError(
15
+ "OutputFormat.JSON is reserved for future Docling export support."
16
+ )
17
+ if output_format is OutputFormat.DOCTAGS:
18
+ raise NotImplementedError(
19
+ "OutputFormat.DOCTAGS is reserved for future Docling export support."
20
+ )
21
+ raise ValueError(f"Unsupported output format: {output_format!r}")
@@ -0,0 +1,15 @@
1
+ from pathlib import Path
2
+
3
+ from ports.parsed_document import ParsedDocument
4
+ from services.pdf_service import PDFService
5
+
6
+
7
+ class PyMuPDFParser:
8
+ """Document parser backed by PyMuPDF plain-text extraction (legacy fallback)."""
9
+
10
+ def __init__(self, pdf_service: PDFService | None = None) -> None:
11
+ self._pdf_service = pdf_service or PDFService()
12
+
13
+ def parse(self, paper_path: Path) -> ParsedDocument:
14
+ text = self._pdf_service.extract(paper_path)
15
+ return ParsedDocument(markdown=text)
agents/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ from agents.coder import Coder
2
+ from agents.planner import Planner
3
+ from agents.reader import Reader
4
+ from agents.reporter import Reporter
5
+ from agents.reviewer import Reviewer
6
+ from agents.runner import Runner
7
+
8
+ __all__ = ["Coder", "Planner", "Reader", "Reporter", "Reviewer", "Runner"]
@@ -0,0 +1,147 @@
1
+ """Build agent-facing context from PaperReproductionAnalysis modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from agents.coder_quality import build_framework_binding
8
+ from models.execution_strategy import ExecutionStrategy
9
+ from models.paper_reproduction_analysis import PaperReproductionAnalysis
10
+ from models.routing import TaskRoutingTable
11
+ from models.task import TaskModel, TaskStep
12
+ from models.verification import VerificationResult
13
+ from routing.task_router import TaskRouter
14
+
15
+
16
+ def build_planner_user_content(execution_strategy: ExecutionStrategy) -> str:
17
+ """Build planner context from committed ExecutionStrategy only."""
18
+ return (
19
+ "Committed execution strategy for task decomposition:\n\n"
20
+ f"Strategy:\n{execution_strategy.strategy.model_dump_json(indent=2)}\n\n"
21
+ f"Resource bindings:\n{execution_strategy.resource_bindings.model_dump_json(indent=2)}\n\n"
22
+ f"Reuse plan:\n{execution_strategy.reuse_plan.model_dump_json(indent=2)}\n\n"
23
+ f"Adaptation plan:\n{execution_strategy.adaptation_plan.model_dump_json(indent=2)}\n\n"
24
+ f"Generation plan:\n{execution_strategy.generation_plan.model_dump_json(indent=2)}\n\n"
25
+ f"Risk assessment:\n{execution_strategy.risk_assessment.model_dump_json(indent=2)}\n\n"
26
+ "Decompose engineering tasks that implement this strategy. "
27
+ "Do not choose repository, greenfield, adaptation, or reuse — those are already decided."
28
+ )
29
+
30
+
31
+ def build_planner_legacy_user_content(analysis: PaperReproductionAnalysis) -> str:
32
+ return (
33
+ "Reproduction analysis for planning:\n\n"
34
+ f"Metadata:\n{analysis.metadata.model_dump_json(indent=2)}\n\n"
35
+ f"Goal:\n{analysis.goal.model_dump_json(indent=2)}\n\n"
36
+ f"Resources:\n{analysis.resources.model_dump_json(indent=2)}\n\n"
37
+ f"Method:\n{analysis.method.model_dump_json(indent=2)}\n\n"
38
+ f"Evaluation:\n{analysis.evaluation.model_dump_json(indent=2)}\n\n"
39
+ f"Reproduction gaps:\n"
40
+ f"{_gaps_json(analysis)}"
41
+ )
42
+
43
+
44
+ def build_reviewer_user_content(
45
+ analysis: PaperReproductionAnalysis,
46
+ task: TaskModel,
47
+ verification_result: VerificationResult,
48
+ ) -> str:
49
+ return (
50
+ "Reproduction analysis:\n\n"
51
+ f"Goal:\n{analysis.goal.model_dump_json(indent=2)}\n\n"
52
+ f"Method:\n{analysis.method.model_dump_json(indent=2)}\n\n"
53
+ f"Evaluation:\n{analysis.evaluation.model_dump_json(indent=2)}\n\n"
54
+ f"Reproduction gaps:\n"
55
+ f"{_gaps_json(analysis)}\n\n"
56
+ "Task plan:\n"
57
+ f"{task.model_dump_json(indent=2)}\n\n"
58
+ "VerificationResult (ground truth):\n"
59
+ f"{verification_result.model_dump_json(indent=2)}"
60
+ )
61
+
62
+
63
+ def build_coder_shared_context(
64
+ analysis: PaperReproductionAnalysis,
65
+ task: TaskModel,
66
+ routing_table: TaskRoutingTable,
67
+ ) -> dict[str, object]:
68
+ targets = routing_table.targets
69
+ framework = analysis.method.framework
70
+ return {
71
+ "paper_title": analysis.metadata.title,
72
+ "framework": framework,
73
+ "framework_binding": build_framework_binding(framework),
74
+ "dataset": _join_resource_names(analysis.resources.datasets),
75
+ "model": _join_resource_names(analysis.resources.models),
76
+ "optimizer": analysis.method.optimizer,
77
+ "loss": analysis.method.loss,
78
+ "training_pipeline": analysis.method.training_pipeline,
79
+ "evaluation_metric": _join_metric_names(analysis.evaluation.metrics),
80
+ "architecture": analysis.method.architecture,
81
+ "data_processing": analysis.method.data_processing,
82
+ "hyperparameters": [
83
+ item.model_dump() for item in analysis.method.hyperparameters
84
+ ],
85
+ "benchmarks": list(analysis.evaluation.benchmarks),
86
+ "reproduction_gaps": [
87
+ item.model_dump() for item in analysis.reproduction_gaps
88
+ ],
89
+ "python_version": f"{sys.version_info.major}.{sys.version_info.minor}",
90
+ "repository_files": [target.relative_path for target in targets],
91
+ "source_modules": [
92
+ target.relative_path.removesuffix(".py").replace("/", ".")
93
+ for target in targets
94
+ if target.file_category == "source"
95
+ ],
96
+ "config_files": [
97
+ target.relative_path
98
+ for target in targets
99
+ if target.file_category == "config"
100
+ ],
101
+ "script_files": [
102
+ target.relative_path
103
+ for target in targets
104
+ if target.file_category == "script"
105
+ ],
106
+ "train_entrypoint": "scripts/train.py",
107
+ "eval_entrypoint": "scripts/evaluate.py",
108
+ "engineering_tasks": [
109
+ {"id": step.id, "name": step.name, "description": step.description}
110
+ for step in task.steps
111
+ ],
112
+ "routing_coverage": _compute_routing_coverage(task, routing_table),
113
+ }
114
+
115
+
116
+ def _gaps_json(analysis: PaperReproductionAnalysis) -> str:
117
+ if not analysis.reproduction_gaps:
118
+ return "[]"
119
+ return "[" + ", ".join(
120
+ gap.model_dump_json() for gap in analysis.reproduction_gaps
121
+ ) + "]"
122
+
123
+
124
+ def _join_resource_names(resources: list) -> str:
125
+ names = [resource.name for resource in resources if resource.name]
126
+ return ", ".join(names)
127
+
128
+
129
+ def _join_metric_names(metrics: list) -> str:
130
+ names = [metric.name for metric in metrics if metric.name]
131
+ return ", ".join(names)
132
+
133
+
134
+ def _compute_routing_coverage(
135
+ task: TaskModel,
136
+ routing_table: TaskRoutingTable,
137
+ ) -> dict[str, object]:
138
+ covered_ids: set[str] = set()
139
+ router = TaskRouter()
140
+ for step in task.steps:
141
+ if router.route_step(step):
142
+ covered_ids.add(step.id)
143
+ unrouted = [step.id for step in task.steps if step.id not in covered_ids]
144
+ return {
145
+ "routed_step_ids": sorted(covered_ids),
146
+ "unrouted_step_ids": unrouted,
147
+ }
@@ -0,0 +1,20 @@
1
+ """Serialize PaperReproductionAnalysis artifacts produced by Reader."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from models.paper_reproduction_analysis import PaperReproductionAnalysis
9
+
10
+
11
+ def analysis_snapshot_dict(analysis: PaperReproductionAnalysis) -> dict:
12
+ return analysis.model_dump(mode="json")
13
+
14
+
15
+ def write_analysis_snapshot(analysis: PaperReproductionAnalysis, path: Path) -> None:
16
+ path.parent.mkdir(parents=True, exist_ok=True)
17
+ path.write_text(
18
+ json.dumps(analysis_snapshot_dict(analysis), indent=2, default=str) + "\n",
19
+ encoding="utf-8",
20
+ )