Buckets:
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| import re | |
| from pathlib import Path | |
| from typing import Any | |
| from n21.config import load_structured, write_json | |
| from n21.settings import CONFIG_ROOT, REPO_ROOT | |
| from observability.audit_log import utc_now | |
| from data_pipeline.pdf_warning_filter import suppress_known_pypdf_pointer_noise | |
| from data_pipeline.repair_coverage import DEFAULT_MINIMUMS, classify_repair_row | |
| SYSTEM_TEMPLATE = ( | |
| "You are Linvest21_FinGPT acting as an institutional {asset_class} {role}. " | |
| "Learn from curated source material, preserve evidence discipline, separate fact from inference, " | |
| "and avoid investment advice." | |
| ) | |
| def sha256_file(path: Path) -> str: | |
| digest = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for block in iter(lambda: handle.read(1024 * 1024), b""): | |
| digest.update(block) | |
| return digest.hexdigest() | |
| def clean_text(text: str) -> str: | |
| text = text.replace("\x00", " ") | |
| text = re.sub(r"[ \t]+", " ", text) | |
| text = re.sub(r"\n{3,}", "\n\n", text) | |
| return text.strip() | |
| def load_role_manifest(role_dir: Path) -> dict[str, dict[str, Any]]: | |
| manifest_path = role_dir / "manifest.json" | |
| if not manifest_path.exists(): | |
| return {} | |
| manifest = json.loads(manifest_path.read_text(encoding="utf-8")) | |
| items = manifest.get("items", []) | |
| return {str(item.get("filename")): item for item in items if item.get("filename")} | |
| def extract_pages(pdf_path: Path) -> list[dict[str, Any]]: | |
| try: | |
| from pypdf import PdfReader | |
| except ModuleNotFoundError as exc: | |
| raise RuntimeError("Missing dependency pypdf. Install with: python -m pip install pypdf") from exc | |
| with suppress_known_pypdf_pointer_noise(): | |
| reader = PdfReader(str(pdf_path)) | |
| pages: list[dict[str, Any]] = [] | |
| for index, page in enumerate(reader.pages, start=1): | |
| try: | |
| text = clean_text(page.extract_text() or "") | |
| except Exception as exc: # pypdf can fail on malformed individual pages. | |
| text = f"[page extraction failed: {type(exc).__name__}: {exc}]" | |
| if text and not text.startswith("[page extraction failed:"): | |
| pages.append({"page": index, "text": text}) | |
| return pages | |
| def chunk_pages(pages: list[dict[str, Any]], *, chunk_chars: int) -> list[dict[str, Any]]: | |
| chunks: list[dict[str, Any]] = [] | |
| current: list[str] = [] | |
| page_start: int | None = None | |
| page_end: int | None = None | |
| def flush() -> None: | |
| nonlocal current, page_start, page_end | |
| if not current or page_start is None or page_end is None: | |
| return | |
| chunks.append({"page_start": page_start, "page_end": page_end, "text": clean_text("\n\n".join(current))}) | |
| current = [] | |
| page_start = None | |
| page_end = None | |
| for page in pages: | |
| page_no = int(page["page"]) | |
| text = str(page["text"]) | |
| if page_start is None: | |
| page_start = page_no | |
| if sum(len(part) for part in current) + len(text) > chunk_chars and current: | |
| flush() | |
| page_start = page_no | |
| current.append(f"[Page {page_no}]\n{text}") | |
| page_end = page_no | |
| flush() | |
| return chunks | |
| def build_record( | |
| *, | |
| asset_class: str, | |
| role: str, | |
| pdf_path: Path, | |
| pdf_sha256: str, | |
| item: dict[str, Any] | None, | |
| chunk: dict[str, Any], | |
| chunk_index: int, | |
| chunk_count: int, | |
| ) -> dict[str, Any]: | |
| title = str((item or {}).get("title") or pdf_path.stem.replace("_", " ")) | |
| author = str((item or {}).get("author") or "unknown") | |
| license_status = str((item or {}).get("license") or "unknown") | |
| excerpt = str(chunk["text"]) | |
| user_prompt = ( | |
| "Convert the following curated equity research source excerpt into reusable Linvest21 research knowledge. " | |
| "Focus on analytical principles, valuation/research process, evidence use, and risk framing.\n\n" | |
| f"Source title: {title}\n" | |
| f"Author/source: {author}\n" | |
| f"Pages: {chunk['page_start']}-{chunk['page_end']}\n\n" | |
| f"Excerpt:\n{excerpt}" | |
| ) | |
| assistant_response = ( | |
| f"Source: {title}\n" | |
| f"Role lens: Linvest21 {asset_class} {role}\n" | |
| f"Evidence pages: {chunk['page_start']}-{chunk['page_end']}\n\n" | |
| "Reusable research knowledge:\n" | |
| f"{excerpt}\n\n" | |
| "Application guardrails:\n" | |
| "- Use this material as research context, not as standalone investment advice.\n" | |
| "- Preserve source attribution when using the idea in a research memo.\n" | |
| "- Separate observed facts, author claims, and Linvest21 inference." | |
| ) | |
| return { | |
| "messages": [ | |
| {"role": "system", "content": SYSTEM_TEMPLATE.format(asset_class=asset_class, role=role.replace("_", " "))}, | |
| {"role": "user", "content": user_prompt}, | |
| {"role": "assistant", "content": assistant_response}, | |
| ], | |
| "metadata": { | |
| "asset_class": asset_class, | |
| "role": role, | |
| "task": "curated_financial_research_sft", | |
| "source_pdf": pdf_path.name, | |
| "source_sha256": pdf_sha256, | |
| "source_title": title, | |
| "source_author": author, | |
| "license": license_status, | |
| "page_start": chunk["page_start"], | |
| "page_end": chunk["page_end"], | |
| "chunk_index": chunk_index, | |
| "chunk_count": chunk_count, | |
| "curated_root": "data/learning", | |
| "policy": "curated_learning_material", | |
| "created_at": utc_now(), | |
| }, | |
| } | |
| def chunk_text(text: str, *, chunk_chars: int) -> list[dict[str, Any]]: | |
| chunks: list[dict[str, Any]] = [] | |
| clean = clean_text(text) | |
| if not clean: | |
| return [] | |
| for index, start in enumerate(range(0, len(clean), chunk_chars), start=1): | |
| chunk = clean[start:start + chunk_chars] | |
| chunks.append({"section_start": index, "section_end": index, "text": clean_text(chunk)}) | |
| return chunks | |
| def build_normalized_record( | |
| *, | |
| asset_class: str, | |
| role: str, | |
| normalized_path: Path, | |
| normalized_sha256: str, | |
| normalized: dict[str, Any], | |
| chunk: dict[str, Any], | |
| chunk_index: int, | |
| chunk_count: int, | |
| ) -> dict[str, Any]: | |
| title = str(normalized.get("source_title") or normalized_path.stem.replace("_", " ")) | |
| source_format = str(normalized.get("format") or "normalized") | |
| license_status = str(normalized.get("license_status") or "unknown") | |
| excerpt = str(chunk["text"]) | |
| user_prompt = ( | |
| "Convert the following normalized Linvest21 source excerpt into reusable investment research knowledge. " | |
| "Focus on analytical principles, evidence use, risk framing, and role-specific workflow.\n\n" | |
| f"Source title: {title}\n" | |
| f"Source format: {source_format}\n" | |
| f"Section: {chunk['section_start']}-{chunk['section_end']}\n\n" | |
| f"Excerpt:\n{excerpt}" | |
| ) | |
| assistant_response = ( | |
| f"Source: {title}\n" | |
| f"Role lens: Linvest21 {asset_class} {role}\n" | |
| f"Evidence section: {chunk['section_start']}-{chunk['section_end']}\n\n" | |
| "Reusable research knowledge:\n" | |
| f"{excerpt}\n\n" | |
| "Application guardrails:\n" | |
| "- Use this material as research context, not as standalone investment advice.\n" | |
| "- Preserve source attribution when using the idea in a research memo.\n" | |
| "- Separate observed facts, author claims, and Linvest21 inference." | |
| ) | |
| return { | |
| "messages": [ | |
| {"role": "system", "content": SYSTEM_TEMPLATE.format(asset_class=asset_class, role=role.replace("_", " "))}, | |
| {"role": "user", "content": user_prompt}, | |
| {"role": "assistant", "content": assistant_response}, | |
| ], | |
| "metadata": { | |
| "asset_class": asset_class, | |
| "role": role, | |
| "task": "normalized_financial_research_sft", | |
| "source_normalized": normalized_path.name, | |
| "source_sha256": normalized_sha256, | |
| "source_title": title, | |
| "source_url": normalized.get("source_url"), | |
| "source_format": source_format, | |
| "license": license_status, | |
| "section_start": chunk["section_start"], | |
| "section_end": chunk["section_end"], | |
| "chunk_index": chunk_index, | |
| "chunk_count": chunk_count, | |
| "curated_root": "data/learning", | |
| "policy": "normalized_policy_approved_material", | |
| "created_at": utc_now(), | |
| }, | |
| } | |
| def write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_text("\n".join(json.dumps(row, ensure_ascii=True, sort_keys=True) for row in rows) + "\n", encoding="utf-8") | |
| def convert_learning_role( | |
| *, | |
| asset_class: str, | |
| role: str, | |
| chunk_chars: int | None = None, | |
| min_text_chars: int | None = None, | |
| skip_existing: bool = True, | |
| ) -> dict[str, Any]: | |
| config = load_structured(CONFIG_ROOT / "data" / "learning_corpus.json") | |
| curated_root = REPO_ROOT / str(config["curated_root"]) | |
| if asset_class not in config["asset_classes"]: | |
| raise ValueError(f"unsupported asset_class {asset_class}; expected one of {config['asset_classes']}") | |
| if role not in config["roles"]: | |
| raise ValueError(f"unsupported role {role}; expected one of {config['roles']}") | |
| chunk_chars = int(chunk_chars or config["jsonl_generation"]["default_chunk_chars"]) | |
| min_text_chars = int(min_text_chars or config["jsonl_generation"]["default_min_text_chars"]) | |
| role_dir = curated_root / asset_class / role | |
| if not role_dir.exists(): | |
| raise FileNotFoundError(f"role directory not found: {role_dir}") | |
| manifest_items = load_role_manifest(role_dir) | |
| pdfs = sorted(role_dir.glob("*.pdf")) | |
| normalized_sources = sorted(role_dir.glob("*.normalized.json")) | |
| combined_rows: list[dict[str, Any]] = [] | |
| pdf_reports: list[dict[str, Any]] = [] | |
| normalized_reports: list[dict[str, Any]] = [] | |
| for pdf_path in pdfs: | |
| pdf_sha = sha256_file(pdf_path) | |
| output_path = pdf_path.with_name(f"{pdf_path.stem}{config['jsonl_generation']['output_suffix']}") | |
| invalid_existing_error: str | None = None | |
| if skip_existing and output_path.exists() and output_path.stat().st_size > 0: | |
| try: | |
| rows = load_jsonl(output_path) | |
| except ValueError as exc: | |
| invalid_existing_error = str(exc) | |
| else: | |
| combined_rows.extend(rows) | |
| pdf_reports.append( | |
| { | |
| "pdf": pdf_path.name, | |
| "status": "reused_existing_jsonl", | |
| "text_chars": None, | |
| "page_count_with_text": None, | |
| "record_count": len(rows), | |
| "output_jsonl": str(output_path), | |
| "sha256": pdf_sha, | |
| } | |
| ) | |
| continue | |
| pages = extract_pages(pdf_path) | |
| text_chars = sum(len(page["text"]) for page in pages) | |
| if text_chars < min_text_chars: | |
| if output_path.exists(): | |
| output_path.unlink() | |
| pdf_reports.append( | |
| { | |
| "pdf": pdf_path.name, | |
| "status": "skipped_insufficient_extractable_text", | |
| "text_chars": text_chars, | |
| "page_count_with_text": len(pages), | |
| "output_jsonl": None, | |
| "sha256": pdf_sha, | |
| } | |
| ) | |
| continue | |
| chunks = chunk_pages(pages, chunk_chars=chunk_chars) | |
| item = manifest_items.get(pdf_path.name) | |
| rows = [ | |
| build_record( | |
| asset_class=asset_class, | |
| role=role, | |
| pdf_path=pdf_path, | |
| pdf_sha256=pdf_sha, | |
| item=item, | |
| chunk=chunk, | |
| chunk_index=index, | |
| chunk_count=len(chunks), | |
| ) | |
| for index, chunk in enumerate(chunks, start=1) | |
| ] | |
| write_jsonl(output_path, rows) | |
| combined_rows.extend(rows) | |
| status = "regenerated_invalid_existing_jsonl" if invalid_existing_error else "converted" | |
| report_item = { | |
| "pdf": pdf_path.name, | |
| "status": status, | |
| "text_chars": text_chars, | |
| "page_count_with_text": len(pages), | |
| "record_count": len(rows), | |
| "output_jsonl": str(output_path), | |
| "sha256": pdf_sha, | |
| } | |
| if invalid_existing_error: | |
| report_item["invalid_existing_error"] = invalid_existing_error | |
| pdf_reports.append( | |
| report_item | |
| ) | |
| for normalized_path in normalized_sources: | |
| normalized_sha = sha256_file(normalized_path) | |
| output_path = normalized_path.with_name(f"{normalized_path.stem}.hf_finetune.jsonl") | |
| invalid_existing_error: str | None = None | |
| if skip_existing and output_path.exists() and output_path.stat().st_size > 0: | |
| try: | |
| rows = load_jsonl(output_path) | |
| except ValueError as exc: | |
| invalid_existing_error = str(exc) | |
| else: | |
| combined_rows.extend(rows) | |
| normalized_reports.append( | |
| { | |
| "normalized_source": normalized_path.name, | |
| "status": "reused_existing_jsonl", | |
| "text_chars": None, | |
| "record_count": len(rows), | |
| "output_jsonl": str(output_path), | |
| "sha256": normalized_sha, | |
| } | |
| ) | |
| continue | |
| normalized = json.loads(normalized_path.read_text(encoding="utf-8")) | |
| if not normalized.get("eligibility", {}).get("training"): | |
| normalized_reports.append( | |
| { | |
| "normalized_source": normalized_path.name, | |
| "status": "skipped_not_training_eligible", | |
| "text_chars": len(str(normalized.get("text") or "")), | |
| "record_count": 0, | |
| "output_jsonl": None, | |
| "sha256": normalized_sha, | |
| "eligibility": normalized.get("eligibility", {}), | |
| } | |
| ) | |
| continue | |
| text = str(normalized.get("text") or "") | |
| text_chars = len(text) | |
| if text_chars < min_text_chars: | |
| normalized_reports.append( | |
| { | |
| "normalized_source": normalized_path.name, | |
| "status": "skipped_insufficient_extractable_text", | |
| "text_chars": text_chars, | |
| "record_count": 0, | |
| "output_jsonl": None, | |
| "sha256": normalized_sha, | |
| } | |
| ) | |
| continue | |
| chunks = chunk_text(text, chunk_chars=chunk_chars) | |
| rows = [ | |
| build_normalized_record( | |
| asset_class=asset_class, | |
| role=role, | |
| normalized_path=normalized_path, | |
| normalized_sha256=normalized_sha, | |
| normalized=normalized, | |
| chunk=chunk, | |
| chunk_index=index, | |
| chunk_count=len(chunks), | |
| ) | |
| for index, chunk in enumerate(chunks, start=1) | |
| ] | |
| write_jsonl(output_path, rows) | |
| combined_rows.extend(rows) | |
| status = "regenerated_invalid_existing_jsonl" if invalid_existing_error else "converted" | |
| item = { | |
| "normalized_source": normalized_path.name, | |
| "status": status, | |
| "text_chars": text_chars, | |
| "record_count": len(rows), | |
| "output_jsonl": str(output_path), | |
| "sha256": normalized_sha, | |
| } | |
| if invalid_existing_error: | |
| item["invalid_existing_error"] = invalid_existing_error | |
| normalized_reports.append(item) | |
| combined_path = role_dir / f"{asset_class}_{role}.hf_finetune.jsonl" | |
| write_jsonl(combined_path, combined_rows) | |
| report = { | |
| "asset_class": asset_class, | |
| "role": role, | |
| "role_dir": str(role_dir), | |
| "combined_jsonl": str(combined_path), | |
| "pdf_count": len(pdfs), | |
| "normalized_source_count": len(normalized_sources), | |
| "converted_pdf_count": sum(1 for item in pdf_reports if item["status"] in {"converted", "regenerated_invalid_existing_jsonl"}), | |
| "converted_normalized_count": sum(1 for item in normalized_reports if item["status"] in {"converted", "regenerated_invalid_existing_jsonl"}), | |
| "reused_pdf_count": sum(1 for item in pdf_reports if item["status"] == "reused_existing_jsonl"), | |
| "reused_normalized_count": sum(1 for item in normalized_reports if item["status"] == "reused_existing_jsonl"), | |
| "skipped_pdf_count": sum( | |
| 1 | |
| for item in pdf_reports | |
| if item["status"] not in {"converted", "regenerated_invalid_existing_jsonl", "reused_existing_jsonl"} | |
| ), | |
| "skipped_normalized_count": sum( | |
| 1 | |
| for item in normalized_reports | |
| if item["status"] not in {"converted", "regenerated_invalid_existing_jsonl", "reused_existing_jsonl"} | |
| ), | |
| "record_count": len(combined_rows), | |
| "chunk_chars": chunk_chars, | |
| "min_text_chars": min_text_chars, | |
| "schema": "huggingface_chat_messages_jsonl", | |
| "reports": pdf_reports, | |
| "normalized_reports": normalized_reports, | |
| "created_at": utc_now(), | |
| } | |
| write_json(role_dir / f"{asset_class}_{role}_conversion_manifest.json", report) | |
| return report | |
| def load_jsonl(path: Path) -> list[dict[str, Any]]: | |
| rows: list[dict[str, Any]] = [] | |
| with path.open("r", encoding="utf-8", newline="") as handle: | |
| for line_no, line in enumerate(handle, start=1): | |
| if not line.strip(): | |
| continue | |
| try: | |
| item = json.loads(line) | |
| except json.JSONDecodeError as exc: | |
| raise ValueError( | |
| f"invalid JSONL at {path}:{line_no}: {exc.msg} " | |
| f"(column {exc.colno}, char {exc.pos})" | |
| ) from exc | |
| if not isinstance(item, dict) or not isinstance(item.get("messages"), list): | |
| raise ValueError(f"invalid Hugging Face chat JSONL at {path}:{line_no}") | |
| rows.append(item) | |
| return rows | |
| def convert_learning_tree( | |
| *, | |
| asset_class: str | None = None, | |
| role: str | None = None, | |
| source_type: str = "pdf", | |
| chunk_chars: int | None = None, | |
| min_text_chars: int | None = None, | |
| skip_existing: bool = True, | |
| ) -> dict[str, Any]: | |
| if source_type.lower() != "pdf": | |
| raise ValueError(f"unsupported source_type for now: {source_type}; currently supported: pdf") | |
| config = load_structured(CONFIG_ROOT / "data" / "learning_corpus.json") | |
| asset_classes = [asset_class] if asset_class else list(config["asset_classes"]) | |
| roles = [role] if role else list(config["roles"]) | |
| reports: list[dict[str, Any]] = [] | |
| for asset in asset_classes: | |
| for role_name in roles: | |
| role_dir = REPO_ROOT / str(config["curated_root"]) / asset / role_name | |
| if not role_dir.exists(): | |
| continue | |
| reports.append( | |
| convert_learning_role( | |
| asset_class=asset, | |
| role=role_name, | |
| chunk_chars=chunk_chars, | |
| min_text_chars=min_text_chars, | |
| skip_existing=skip_existing, | |
| ) | |
| ) | |
| return { | |
| "source_type": source_type, | |
| "asset_class": asset_class or "all", | |
| "role": role or "all", | |
| "role_count": len(reports), | |
| "pdf_count": sum(int(item["pdf_count"]) for item in reports), | |
| "normalized_source_count": sum(int(item.get("normalized_source_count", 0)) for item in reports), | |
| "converted_pdf_count": sum(int(item["converted_pdf_count"]) for item in reports), | |
| "converted_normalized_count": sum(int(item.get("converted_normalized_count", 0)) for item in reports), | |
| "reused_pdf_count": sum(int(item.get("reused_pdf_count", 0)) for item in reports), | |
| "reused_normalized_count": sum(int(item.get("reused_normalized_count", 0)) for item in reports), | |
| "skipped_pdf_count": sum(int(item["skipped_pdf_count"]) for item in reports), | |
| "skipped_normalized_count": sum(int(item.get("skipped_normalized_count", 0)) for item in reports), | |
| "record_count": sum(int(item["record_count"]) for item in reports), | |
| "reports": reports, | |
| "created_at": utc_now(), | |
| } | |
| def build_training_jsonl_from_learning( | |
| *, | |
| output_path: Path, | |
| source: Path | None = None, | |
| asset_class: str | None = None, | |
| role: str | None = None, | |
| repair_oversample_factor: int = 1, | |
| max_repair_selected_ratio: float | None = None, | |
| ) -> dict[str, Any]: | |
| config = load_structured(CONFIG_ROOT / "data" / "learning_corpus.json") | |
| curated_root = REPO_ROOT / str(config["curated_root"]) | |
| if source is None: | |
| source_path = curated_root.resolve() | |
| elif source.is_absolute(): | |
| source_path = source.resolve() | |
| else: | |
| source_path = (REPO_ROOT / source).resolve() | |
| output_path = output_path.resolve() | |
| suffix = str(config["jsonl_generation"]["output_suffix"]) | |
| if source_path.is_file(): | |
| jsonls = [source_path] | |
| elif source_path.is_dir(): | |
| jsonls = sorted(source_path.rglob(f"*{suffix}")) | |
| combined_names = {f"{asset}_{role_name}{suffix}" for asset in config["asset_classes"] for role_name in config["roles"]} | |
| jsonls = [ | |
| item | |
| for item in jsonls | |
| if item.name not in combined_names | |
| ] | |
| else: | |
| raise FileNotFoundError(f"learning source path not found: {source_path}") | |
| selected: list[Path] = [] | |
| rows: list[dict[str, Any]] = [] | |
| for jsonl in jsonls: | |
| loaded = load_jsonl(jsonl) | |
| if asset_class or role: | |
| filtered = [ | |
| row | |
| for row in loaded | |
| if (asset_class is None or row.get("metadata", {}).get("asset_class") == asset_class) | |
| and (role is None or row.get("metadata", {}).get("role") == role) | |
| ] | |
| else: | |
| filtered = loaded | |
| if not filtered: | |
| continue | |
| selected.append(jsonl) | |
| rows.extend(filtered) | |
| if not rows: | |
| raise ValueError(f"no training rows selected from {source_path}") | |
| repair_oversample_factor = max(1, int(repair_oversample_factor or 1)) | |
| source_record_count = len(rows) | |
| repair_classifications = [classify_repair_row(row) for row in rows] | |
| source_repair_row_count = sum(1 for classes in repair_classifications if classes) | |
| repair_cap_applied = max_repair_selected_ratio is not None | |
| selected_repair_source_rows = source_repair_row_count | |
| dropped_repair_source_rows = 0 | |
| if max_repair_selected_ratio is not None: | |
| max_repair_selected_ratio = float(max_repair_selected_ratio) | |
| if max_repair_selected_ratio <= 0 or max_repair_selected_ratio >= 1: | |
| raise ValueError("--max-repair-selected-ratio must be greater than 0 and less than 1") | |
| nonrepair_rows: list[dict[str, Any]] = [] | |
| repair_candidates: list[tuple[int, dict[str, Any], set[str]]] = [] | |
| for index, (row, classes) in enumerate(zip(rows, repair_classifications)): | |
| if classes: | |
| repair_candidates.append((index, row, classes)) | |
| else: | |
| nonrepair_rows.append(row) | |
| selected_indexes: set[int] = set() | |
| selected_counts = {key: 0 for key in DEFAULT_MINIMUMS} | |
| for repair_class, minimum in DEFAULT_MINIMUMS.items(): | |
| for index, _row, classes in repair_candidates: | |
| if selected_counts[repair_class] * repair_oversample_factor >= minimum: | |
| break | |
| if repair_class not in classes: | |
| continue | |
| selected_indexes.add(index) | |
| for item in classes: | |
| if item in selected_counts: | |
| selected_counts[item] += 1 | |
| missing = { | |
| key: { | |
| "selected_base_rows": value, | |
| "effective_rows_after_oversample": value * repair_oversample_factor, | |
| "minimum": DEFAULT_MINIMUMS[key], | |
| } | |
| for key, value in selected_counts.items() | |
| if value * repair_oversample_factor < DEFAULT_MINIMUMS[key] | |
| } | |
| if missing: | |
| raise ValueError( | |
| "repair cap cannot be applied because selected repair rows cannot satisfy coverage minimums: " | |
| + json.dumps(missing, sort_keys=True) | |
| ) | |
| selected_repair_rows = [row for index, row, _classes in repair_candidates if index in selected_indexes] | |
| effective_repair_rows = len(selected_repair_rows) * repair_oversample_factor | |
| final_record_count = len(nonrepair_rows) + effective_repair_rows | |
| repair_ratio = 0 if final_record_count == 0 else effective_repair_rows / final_record_count | |
| if repair_ratio > max_repair_selected_ratio: | |
| raise ValueError( | |
| "repair cap cannot be applied while preserving repair coverage: " | |
| f"effective_repair_rows={effective_repair_rows}, nonrepair_rows={len(nonrepair_rows)}, " | |
| f"ratio={repair_ratio:.6f}, max_repair_selected_ratio={max_repair_selected_ratio:.6f}. " | |
| "Add more original non-repair training rows or lower repair coverage minimums only by explicit policy." | |
| ) | |
| rows = nonrepair_rows + selected_repair_rows | |
| repair_classifications = [classify_repair_row(row) for row in rows] | |
| selected_repair_source_rows = len(selected_repair_rows) | |
| dropped_repair_source_rows = source_repair_row_count - selected_repair_source_rows | |
| original_record_count = len(rows) | |
| repair_row_count = 0 | |
| oversampled_rows: list[dict[str, Any]] = [] | |
| if repair_oversample_factor > 1: | |
| for row in rows: | |
| repair_classes = sorted(classify_repair_row(row)) | |
| if not repair_classes: | |
| continue | |
| repair_row_count += 1 | |
| for copy_index in range(2, repair_oversample_factor + 1): | |
| cloned = json.loads(json.dumps(row)) | |
| metadata = cloned.setdefault("metadata", {}) | |
| if isinstance(metadata, dict): | |
| metadata["repair_oversample_copy"] = copy_index | |
| metadata["repair_oversample_factor"] = repair_oversample_factor | |
| metadata["repair_oversample_classes"] = repair_classes | |
| oversampled_rows.append(cloned) | |
| rows.extend(oversampled_rows) | |
| else: | |
| repair_row_count = sum(1 for row in rows if classify_repair_row(row)) | |
| required_reasoning_files: list[Path] = [] | |
| if asset_class and role: | |
| role_dir = curated_root / asset_class / role | |
| if role_dir.exists(): | |
| required_reasoning_files = sorted(role_dir.glob(f"synthetic_{asset_class}_{role}_critical_reasoning{suffix}")) | |
| selected_resolved = {path.resolve() for path in selected} | |
| missing_required_reasoning = [path for path in required_reasoning_files if path.resolve() not in selected_resolved] | |
| if missing_required_reasoning: | |
| missing = ", ".join(str(path) for path in missing_required_reasoning) | |
| raise ValueError(f"required role-grounded reasoning JSONL was not selected for training: {missing}") | |
| write_jsonl(output_path, rows) | |
| selected_training_sha256 = sha256_file(output_path) | |
| manifest = { | |
| "schema_version": "selected_training_manifest_v1", | |
| "output_jsonl": str(output_path), | |
| "source": str(source_path), | |
| "asset_class": asset_class or "all", | |
| "role": role or "all", | |
| "input_jsonl_count": len(selected), | |
| "record_count": len(rows), | |
| "source_record_count": source_record_count, | |
| "original_record_count": original_record_count, | |
| "repair_row_count": repair_row_count, | |
| "source_repair_row_count": source_repair_row_count, | |
| "selected_repair_source_rows": selected_repair_source_rows, | |
| "dropped_repair_source_rows": dropped_repair_source_rows, | |
| "repair_cap_applied": repair_cap_applied, | |
| "max_repair_selected_ratio": max_repair_selected_ratio, | |
| "repair_oversample_factor": repair_oversample_factor, | |
| "repair_oversampled_record_count": len(oversampled_rows), | |
| "input_jsonls": [str(path) for path in selected], | |
| "selected_training_sha256": selected_training_sha256, | |
| "required_reasoning_jsonls": [str(path) for path in required_reasoning_files], | |
| "required_reasoning_included": not missing_required_reasoning, | |
| "created_at": utc_now(), | |
| } | |
| write_json(output_path.with_suffix(".manifest.json"), manifest) | |
| return manifest | |
Xet Storage Details
- Size:
- 29.2 kB
- Xet hash:
- 2fa839100c8ab9fc328a7fe9043bd982d08f738511e59318306bc3fdb9b89ea1
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.