sofhiaazzhr Claude Opus 4.8 commited on
Commit
717452f
·
1 Parent(s): 6856ea8

[NOTICKET] feat(knowledge-parsing): add 1-based page_no (S6b)

Browse files

A computed `page_no` (= page_idx + 1) on Chunk, so the human/review-UI page
number lives in the artifact instead of each consumer converting it.

- Derived, never stored: cannot drift from page_idx, and neither normaliser has
to set it. page_idx stays 0-based and raw.
- Additive, schema 0.3.2 -> 0.3.3; older artifacts and consumers are unaffected.
- Closes S6b (open since 2026-08-19); reverses the earlier "convert in the UI"
note now that a real reviewer is about to read these.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (1) hide show
  1. src/knowledge_parsing/contracts.py +21 -9
src/knowledge_parsing/contracts.py CHANGED
@@ -39,15 +39,16 @@ from __future__ import annotations
39
 
40
  from typing import Literal
41
 
42
- from pydantic import BaseModel, Field
43
 
44
  ChunkKind = Literal["text", "table", "chart", "equation", "image"]
45
 
 
46
  # 0.3.2 — adds the optional `source_title`.
47
  # 0.3.1 — adds the optional `normaliser_version`.
48
- # Both patch bumps: purely additive, so every 0.3.0 consumer reads them unchanged
49
  # and the new fields are null on anything written earlier.
50
- SCHEMA_VERSION = "0.3.2"
51
 
52
 
53
  class Chunk(BaseModel):
@@ -70,15 +71,26 @@ class Chunk(BaseModel):
70
 
71
  # Location in the source document.
72
  #
73
- # Named `page_idx` deliberately: these are 0-BASED, exactly as MinerU reports
74
- # them, with no conversion anywhere in the pipeline. Page numbers eventually
75
- # reach a human review queue, and an off-by-one there is invisible until an
76
- # expert opens the wrong page. Converting to 1-based is the UI's job, done
77
- # once at display time — never here, so the artifact always matches the raw
78
- # MinerU output kept alongside it.
79
  page_idx: int
80
  page_idxs: list[int] = Field(default_factory=list)
81
 
 
 
 
 
 
 
 
 
 
 
 
82
  # Structural context. All Optional — many documents carry no headings.
83
  section_no: str | None = None # e.g. "2.1.3", when the document is numbered
84
 
 
39
 
40
  from typing import Literal
41
 
42
+ from pydantic import BaseModel, Field, computed_field
43
 
44
  ChunkKind = Literal["text", "table", "chart", "equation", "image"]
45
 
46
+ # 0.3.3 — adds the computed 1-based `page_no` (S6b). Derived from `page_idx`.
47
  # 0.3.2 — adds the optional `source_title`.
48
  # 0.3.1 — adds the optional `normaliser_version`.
49
+ # All patch bumps: purely additive, so every 0.3.0 consumer reads them unchanged
50
  # and the new fields are null on anything written earlier.
51
+ SCHEMA_VERSION = "0.3.3"
52
 
53
 
54
  class Chunk(BaseModel):
 
71
 
72
  # Location in the source document.
73
  #
74
+ # Named `page_idx` deliberately: these are 0-BASED, exactly as the parser
75
+ # reports them, with no conversion — so the artifact always matches the raw
76
+ # parser output kept alongside it. The 1-based number a human reads is
77
+ # `page_no` below: a review queue is about to see these, and an off-by-one
78
+ # there is invisible until an expert opens the wrong page, so the conversion
79
+ # is done once, here, rather than left to each consumer (S6b, 2026-09-02).
80
  page_idx: int
81
  page_idxs: list[int] = Field(default_factory=list)
82
 
83
+ @computed_field # type: ignore[prop-decorator]
84
+ @property
85
+ def page_no(self) -> int:
86
+ """1-based page number, for humans and the review UI.
87
+
88
+ Derived from `page_idx`, never stored, so it cannot drift from it — and
89
+ neither normaliser has to remember to set it. `page_idx` stays 0-based
90
+ and raw; this is the single number the reviewer sees.
91
+ """
92
+ return self.page_idx + 1
93
+
94
  # Structural context. All Optional — many documents carry no headings.
95
  section_no: str | None = None # e.g. "2.1.3", when the document is numbered
96