Convert a document and integrate it into the knowledge base. Steps: 1. Hash-check — skip if already known. 2. Copy source to ``raw/``. 3. If PDF and page count >= threshold → return :attr:`ConvertResult.is_long_doc`. 4. If ``.md`` — read, process relative images, save to ``wiki/
(
src: Path,
kb_dir: Path,
*,
staging_dir: Path | None = None,
)
| 140 | |
| 141 | |
| 142 | def convert_document( |
| 143 | src: Path, |
| 144 | kb_dir: Path, |
| 145 | *, |
| 146 | staging_dir: Path | None = None, |
| 147 | ) -> ConvertResult: |
| 148 | """Convert a document and integrate it into the knowledge base. |
| 149 | |
| 150 | Steps: |
| 151 | 1. Hash-check — skip if already known. |
| 152 | 2. Copy source to ``raw/``. |
| 153 | 3. If PDF and page count >= threshold → return :attr:`ConvertResult.is_long_doc`. |
| 154 | 4. If ``.md`` — read, process relative images, save to ``wiki/sources/``. |
| 155 | 5. Otherwise — run MarkItDown, extract base64 images, save to ``wiki/sources/``. |
| 156 | 6. Register hash in the registry. |
| 157 | """ |
| 158 | with kb_ingest_lock(kb_dir / ".openkb"): |
| 159 | # ------------------------------------------------------------------ |
| 160 | # Load config & state |
| 161 | # ------------------------------------------------------------------ |
| 162 | openkb_dir = kb_dir / ".openkb" |
| 163 | config = load_config(openkb_dir / "config.yaml") |
| 164 | threshold: int = config.get("pageindex_threshold", 20) |
| 165 | artifact_root = staging_dir if staging_dir is not None else kb_dir |
| 166 | registry = HashRegistry(openkb_dir / "hashes.json") |
| 167 | |
| 168 | # ------------------------------------------------------------------ |
| 169 | # 1. Hash check + identity resolution |
| 170 | # ------------------------------------------------------------------ |
| 171 | file_hash = HashRegistry.hash_file(src) |
| 172 | if registry.is_known(file_hash): |
| 173 | logger.info("Skipping already-known file: %s", src.name) |
| 174 | stored = registry.get(file_hash) or {} |
| 175 | return ConvertResult( |
| 176 | skipped=True, |
| 177 | file_hash=file_hash, |
| 178 | doc_name=stored.get("doc_name") or Path(stored.get("name", src.name)).stem, |
| 179 | ) |
| 180 | doc_name = resolve_doc_name( |
| 181 | src, |
| 182 | kb_dir, |
| 183 | registry, |
| 184 | persist_legacy=staging_dir is None, |
| 185 | ) |
| 186 | |
| 187 | # ------------------------------------------------------------------ |
| 188 | # 2. Copy to raw/ |
| 189 | # ------------------------------------------------------------------ |
| 190 | raw_dir = artifact_root / "raw" |
| 191 | raw_dir.mkdir(parents=True, exist_ok=True) |
| 192 | if staging_dir is None and src.resolve().is_relative_to(raw_dir.resolve()): |
| 193 | # Watch mode: the file already lives in raw/ — don't copy/rename. |
| 194 | raw_dest = src |
| 195 | else: |
| 196 | raw_dest = raw_dir / f"{doc_name}{src.suffix.lower()}" |
| 197 | shutil.copy2(src, raw_dest) |
| 198 | |
| 199 | # ------------------------------------------------------------------ |