diff

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package diff compares two normalized documents and emits a structured change set: numeric statement deltas matched by concept (labels drift, concepts are stable) and narrative changes matched by item id and diffed at paragraph granularity. It is a pure function over two *model.Document values — it never fetches or re-parses — so a diff is just another schema-stamped artifact the same way a document is. It depends only on internal/model and the standard library.

Index

Constants

This section is empty.

Variables

View Source
var ErrNilDocument = errors.New("diff: nil document")

ErrNilDocument is returned when either side of the diff is nil.

View Source
var ErrSemanticNotImplemented = errors.New(
	"sec-cli: --layer semantic is not yet implemented; use --layer structural (default) or --layer lexical. " +
		"Semantic embedding-distance ranking is planned for v1.0.1.",
)

ErrSemanticNotImplemented is returned when --layer semantic is requested. Semantic (embedding-distance) ranking is deferred to v1.0.1: shipping a half-baked embedding pipeline would compromise the "no LLM dependency in the core" principle. The structural layer surfaces what changed; the lexical layer shows exactly how. The semantic ranking layer (which paragraphs changed the most in meaning, noise-filtered) is a planned follow-up.

View Source
var ErrUnknownFormat = errors.New("diff: unknown format")

ErrUnknownFormat is returned by RendererFor for an unrecognized format name.

Functions

func DiffLexical

func DiffLexical(prev, curr string) string

DiffLexical returns a human-readable word-level diff between two paragraph texts. Equal spans are shown in-line; insertions are wrapped in [+...+] and deletions in [-...-]. The result is suitable for the --layer lexical view where an analyst wants to compare specific wording changes.

func DiffWithLayer

func DiffWithLayer(prev, curr *model.Document, layer Layer) (*ChangeSet, []LexicalSectionDiff, error)

DiffWithLayer runs a structural diff and, for the lexical layer, annotates modified paragraphs with word-level diffs. The semantic layer returns ErrSemanticNotImplemented.

Types

type CellDelta

type CellDelta struct {
	Period string   `json:"period"`
	Prev   *float64 `json:"prev"`
	Curr   *float64 `json:"curr"`
	Abs    *float64 `json:"abs"`
	Pct    *float64 `json:"pct"`
}

CellDelta is the change to one concept in one overlapping period: the two values and their absolute and percentage change. Any field is null when it cannot be computed — a missing value is null (never 0), and Pct is null when the previous value is absent or zero. Pct is a percentage (e.g. 11.5 means +11.5%), not a fraction.

type ChangeKind

type ChangeKind string

ChangeKind classifies a single change. A row or paragraph is added when it appears only in the current filing, removed when only in the previous, changed when present in both with differing values, and unchanged when present in both with identical values.

const (
	Added     ChangeKind = "added"
	Removed   ChangeKind = "removed"
	Changed   ChangeKind = "changed"
	Unchanged ChangeKind = "unchanged"
)

Change kinds.

type ChangeSet

type ChangeSet struct {
	SchemaVersion string          `json:"schema_version"`
	Prev          FilingRef       `json:"prev"`
	Curr          FilingRef       `json:"curr"`
	Statements    []StatementDiff `json:"statements"`
	Sections      []SectionDiff   `json:"sections"`
}

ChangeSet is the structured result of diffing two documents: what filings were compared, the per-statement numeric deltas, and the per-section narrative changes. It carries its own SchemaVersion so a rendered diff is a versioned artifact like a Document.

func Diff

func Diff(prev, curr *model.Document) (*ChangeSet, error)

Diff compares two assembled documents and returns the change set. prev is the earlier filing, curr the later one. It never fetches or re-parses; it only reads the two documents.

type FilingRef

type FilingRef struct {
	Company    string `json:"company,omitempty"`
	Ticker     string `json:"ticker,omitempty"`
	Form       string `json:"form,omitempty"`
	Accession  string `json:"accession,omitempty"`
	FilingDate string `json:"filing_date,omitempty"`
	PeriodEnd  string `json:"period_end,omitempty"`
}

FilingRef identifies one side of the comparison — enough to label the report without carrying the whole document.

type JSON

type JSON struct{}

JSON renders the canonical, schema-stable serialization of a change set: deterministic field order, HTML left unescaped so paragraph text stays readable, and a trailing newline.

func (JSON) Render

func (JSON) Render(cs *ChangeSet, w io.Writer) error

Render writes cs as indented JSON.

type Layer

type Layer string

Layer selects the granularity of the narrative diff.

const (
	LayerStructural Layer = "structural" // subsection-grain add/remove/modify (default)
	LayerSemantic   Layer = "semantic"   // embedding-distance ranking (v1.0.1, not yet implemented)
	LayerLexical    Layer = "lexical"    // word-level diff via diffmatchpatch
)

Layer constants match the --layer flag values from DESIGN.md.

type LexicalParagraph

type LexicalParagraph struct {
	Text string `json:"text"`
}

LexicalParagraph is one paragraph's word-level diff. Text carries the annotated form: equal spans are plain, insertions are [+..+], deletions [-...-].

type LexicalRenderer

type LexicalRenderer struct{}

LexicalRenderer writes a lexical diff report — per-section word-level diffs — as plain Markdown. It is used when --layer lexical is requested and wraps both the structural ChangeSet header and the word-level paragraph diffs.

func (LexicalRenderer) Render

func (LexicalRenderer) Render(cs *ChangeSet, lexical []LexicalSectionDiff, w io.Writer) error

Render writes the lexical diff as Markdown.

type LexicalSectionDiff

type LexicalSectionDiff struct {
	Item       string             `json:"item,omitempty"`
	Title      string             `json:"title,omitempty"`
	Paragraphs []LexicalParagraph `json:"paragraphs,omitempty"`
}

LexicalSectionDiff is the lexical-layer change to one section: the section coordinates and the word-level diff strings for paragraphs that changed.

func DiffLexicalSections

func DiffLexicalSections(cs *ChangeSet, prev, curr []string) []LexicalSectionDiff

DiffLexicalSections produces a word-level diff for the modified sections from a structural ChangeSet. Added/removed sections are omitted (their full text appears in the structural report); only sections with at least one changed paragraph are included.

type Markdown

type Markdown struct{}

Markdown renders the human- and LLM-facing "what changed" report: a heading naming the two filings, a deltas table per statement (changed/added/removed rows only — unchanged lines are omitted so the change stands out), and a bulleted list of added/removed paragraphs per section.

func (Markdown) Render

func (Markdown) Render(cs *ChangeSet, w io.Writer) error

Render writes cs as a Markdown report.

type ParagraphChange

type ParagraphChange struct {
	Kind ChangeKind `json:"kind"`
	Text string     `json:"text"`
}

ParagraphChange is one paragraph added or removed from a section's free text.

type Renderer

type Renderer interface {
	Render(cs *ChangeSet, w io.Writer) error
}

Renderer writes one serialization of a ChangeSet to w. It mirrors the shape of internal/render's Renderer (a total function over the artifact) so a diff renders the same way a document does — JSON is the canonical contract, Markdown the human- and LLM-facing "what changed" report.

func RendererFor

func RendererFor(format string) (Renderer, error)

RendererFor returns the renderer for a format name: "json" (the default) or "md"/"markdown". Text is intentionally not offered — a diff is a report, and its two useful forms are the canonical JSON and the Markdown summary.

type RowDelta

type RowDelta struct {
	Concept string      `json:"concept,omitempty"`
	Label   string      `json:"label"`
	Status  ChangeKind  `json:"status"`
	Cells   []CellDelta `json:"cells"`
}

RowDelta is one statement line matched across the two filings by concept (so a relabeled line still matches). Status summarizes the row; Cells carries the per-period numbers.

type SectionDiff

type SectionDiff struct {
	Item       string            `json:"item,omitempty"`
	Title      string            `json:"title,omitempty"`
	Paragraphs []ParagraphChange `json:"paragraphs,omitempty"`
}

SectionDiff is the narrative change to one section, matched between filings by item id. Paragraphs lists the added and removed paragraphs in merge order; a reworded paragraph surfaces as a removed/added pair.

type StatementDiff

type StatementDiff struct {
	Title   string     `json:"title,omitempty"`
	RoleURI string     `json:"role_uri,omitempty"`
	Periods []string   `json:"periods"`
	Rows    []RowDelta `json:"rows"`
}

StatementDiff is the change to one financial statement, matched between the two filings by role URI (or title when no role). Periods are the reporting periods present in both filings, in current-filing order; every RowDelta's cells align to them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL