Class: Ucode::CodeChart::Provenance

Inherits:
Lutaml::Model::Serializable
  • Object
show all
Defined in:
lib/ucode/code_chart/provenance.rb

Overview

Per-codepoint provenance value object — every field the REQ (R6) requires in the sidecar JSON next to each extracted SVG.

The single source of truth for the sidecar schema: a lutaml-model class with an explicit key_value mapping block. Adding a field = one attribute declaration + one map line in the mapping block. The Sidecar writer calls to_hash (framework-provided); no hand-rolled to_h anywhere.

extractor_version reads from Ucode::VERSION at construction so the field stays in sync with the gem's version bump — single source of truth.

extracted_at is the extraction event timestamp (UTC ISO8601), not the file-write timestamp.

Class Method Summary collapse

Class Method Details

.build(block:, codepoint:, ucd_version:, pdf_path:, pdf_sha: nil, now: nil, base_font: nil, gid: nil, source_page: nil, source_cell: nil) ⇒ Provenance

Builds a Provenance from the inputs the Writer has on hand (block, codepoint, ucd_version, pdf_path). Computes the PDF hash + URL once. The extracted_at timestamp is fixed at call time so re-running the same block produces identical provenance JSON for unchanged codepoints.

The optional base_font, gid, source_page, source_cell come from the Extractor::Result when available. Nil values are emitted as JSON null — preserves the audit trail's honesty about what the extractor actually knew.

Parameters:

  • block (Ucode::Models::Block)
  • codepoint (Integer)
  • ucd_version (String)
  • pdf_path (Pathname, String)
  • pdf_sha (String, nil) (defaults to: nil)

    pre-computed sha256 hex digest. When nil, computed from pdf_path. The Writer passes its already-computed summary hash to avoid re-reading the PDF per codepoint.

  • now (Time, nil) (defaults to: nil)

    override for tests

  • base_font (String, nil) (defaults to: nil)

    PDF BaseFont name

  • gid (Integer, nil) (defaults to: nil)

    GID inside that font

  • source_page (Integer, nil) (defaults to: nil)

    1-based PDF page number

  • source_cell (Hash{Symbol=>Float}, nil) (defaults to: nil)

    {x:, y:} PDF user space coordinates of the specimen

Returns:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ucode/code_chart/provenance.rb', line 93

def self.build(block:, codepoint:, ucd_version:, pdf_path:,
               pdf_sha: nil, now: nil,
               base_font: nil, gid: nil,
               source_page: nil, source_cell: nil)
  path = Pathname.new(pdf_path)
  new(
    codepoint: format("U+%04X", codepoint),
    block: block.id,
    source_pdf_url: code_chart_url(block.range_first),
    source_pdf_sha256: pdf_sha || sha256_of(path),
    ucd_version: ucd_version,
    extracted_at: (now || Time.now.utc).iso8601,
    extractor_version: Ucode::VERSION,
    base_font: base_font,
    gid: gid,
    source_page: source_page,
    source_cell: source_cell,
  )
end

.code_chart_url(block_first_cp) ⇒ String

Computes the source PDF's URL from a block name and first codepoint. Mirrors the per-block URL convention in Fetch::CodeCharts: the hex representation of the codepoint, zero-padded to a minimum of 4 digits (e.g. U0000.pdf for BMP, U10920.pdf for Plane 1, U100000.pdf for Plane 16 SPUA-B).

Parameters:

  • block_first_cp (Integer)

Returns:

  • (String)


62
63
64
65
# File 'lib/ucode/code_chart/provenance.rb', line 62

def self.code_chart_url(block_first_cp)
  slug = block_first_cp.to_s(16).upcase.rjust(4, "0")
  "#{Ucode.configuration.charts_base_url}/U#{slug}.pdf"
end

.sha256_of(path) ⇒ String

Returns hex digest, "" when the path doesn't exist (callers can decide how to handle a missing hash).

Parameters:

  • path (Pathname)

Returns:

  • (String)

    hex digest, "" when the path doesn't exist (callers can decide how to handle a missing hash)



116
117
118
119
120
# File 'lib/ucode/code_chart/provenance.rb', line 116

def self.sha256_of(path)
  return "" unless path.exist?

  Digest::SHA256.file(path).hexdigest
end