Verification modules
Generating a diagram is half the job. These modules are the other half: what to check, what the check can and cannot see, and how to stop. The loop is bounded at three correction passes.
The stale-render trap. EA caches a diagram's render. If rows changed since EA last drew it (a direct database write, a placement from another session) a render request can return the previous image, which looks plausible. A check that skips the reload certifies a diagram it never saw. verify_diagram reloads unconditionally and has no way to skip it. get_diagram_png and get_diagram_svg are for when you already trust the state; both can be asked to refresh. The SVG is EA's own drawing, so it is authoritative for color and paint order: a color read back proves nothing because EA echoes what was written.
The diagram check (lint.py)
What it does, and why it matters to you. This is the check half of the generate-check-correct loop. It judges a diagram against itself (intrinsic quality) and consumes exactly what verify_diagram returns, making no repository calls, so it runs anywhere that result can be carried. It is language-neutral by construction: every rule reads geometry and presentation, never the metamodel, and no notation name appears in the module (a test enforces it over identifiers and string constants). Its report tells the AI, and you, which defects it found and which it could not look for.
Logic: Rule-based. Repeatable, but it can be wrong, and a clean report means "no defect this checker knows how to measure", not "this diagram reads well".
What it takes in and produces
- In: the
verify_diagramresult (objects with element id, rectangle, name, type and fill; links with connector id, whether stored, route, endpoints and name; the canvas; and optionally the SVG text); optionally a presentation profile; the groupings the AI declares (roles, rows, columns, stacks, rings); an expected route; a title convention. - Out: a report holding the findings (each with a rule, a severity, a message, the elements involved and a suggested correction), the metrics with their denominators, the rules a profile suppressed, the rules that could not run, and whether the report is "clean" (no errors) and whether a pass changed nothing (the stall test).
- Tolerances: sizes within 4 units count as the same; spacing within 8 units counts as even; ring angles within 4 degrees (calibrated over 6300 ring layouts: rounding noise at 0.78 degrees, a 4-unit nudge at 3.47, the smallest measured defect at 60); a label margin of 8 pixels (calibrated on a real capability map: the two names that looked wrong had margins 5 and 6, the next tightest 10). The correction budget is 3 passes.
Used by. ea-diagram-composition (step 5).
What it catches, grouped. Eighteen rules, in five plain groups. Severity is as the diagram check assigns it.
Overlap and the canvas
- Elements sharing space where neither contains the other (
overlap, error). Containment is exempt and so is touching. - Elements outside the diagram's page size (
out-of-canvas, information). Image export follows the content and is not clipped; this matters only for printing.
Consistent sizing and spacing (these run only on groupings the AI declares)
- Elements in one role that differ in size by more than 4 units (
uniform-sizing, error). - Uneven spacing in a row or column of three or more: neighbors touching or overlapping (error, even when the spacing is even), or a spread of spacing above 8 units (warning) (
pitch). It compares gaps for uniform sizes and center pitch for mixed sizes. - Containers in a stack that differ in the cross-axis extent (
ragged-stack, warning), or that are equal in size but start at different leading edges so the followed edge zigzags (staggered-stack, warning). - Items on a ring at unequal drawn distances from the hub edge (
uneven-spokes, warning), or bunched to one side (uneven-ring-angles, warning; a partial sweep drops its opening before judging).
Connectors
- Connectors with no route set, so the diagram depends on EA's default (
routing-unset, warning), or not using the expected route (routing-mismatch, warning). - A count of crossings (
crossings, information), measured center to center. It cannot tell a removable crossing from a necessary one; compare between passes. - Some connectors named and some not (
missing-connector-labels, warning). Uniformly unlabeled is a convention, not a finding, and a profile that hides labels suppresses this rule. - A profile that collapses parallel connectors with no annotation in any element or connector name (words such as collapsed, merged, aggregated, simplified or summarized) (
collapsed-connectors-unannotated, error). It is the only rule a profile turns on.
Color and titles
- Two or more fills with no legend and no named container explaining them (
unexplained-color, warning). - A drawn title is expected and none exists: no Text, Note or Boundary element (
missing-title, warning).
Text fit (these need the SVG)
- EA drew an element wider than its stored rectangle because its name cannot break (
render-exceeds-rect, error). EA grows the box and does not clip, so every stored-geometry rule is blind to it. - Text overflows its box and is cut off in an export (
label-clipped, error). - Text fits with under 8 pixels to spare, reading as touching the border (
label-cramped, warning).
Metrics and denominators. A count with no denominator is how a vacuous zero gets quoted, so the report carries denominators: the number of objects and links; overlaps; elements outside the canvas; the worst spacing spread and how many spacing groups were measured; roles with inconsistent sizing and how many roles were measured; links stored and unstyled; crossings, with a flag that they are approximate and what they were measured over; distinct fills; whether a title convention was stated; unlabeled connectors; labels measured (boxes judged, not elements checked) and labels unmatched; whether a collapse was annotated; and the error and finding counts. The stack and ring metrics are absent when the rule did not run.
How it behaves. Overlap uses EA's coordinate convention. A rule that did not run says so: the report lists a grouping the caller declared and the rule could not judge (an unknown axis, a ring with no sweep, groups too small to compare, no SVG), while a grouping that was not supplied says nothing, because four lines on every ordinary diagram would make the list one nobody reads. The label rules compare EA's own measured text width in the SVG with EA's rectangles. They refuse to guess: only a box whose text is exactly one known element's name is judged, and a name two elements share identifies neither, so such elements are counted as unmatched. On a notation drawn with shape scripts (paths, not rectangles) the rules stay mostly silent. A profile can switch off only what a view deliberately hides and never a structural defect; the suppressed rules are returned on the report.
Limitations. What it cannot see is kept in the composition skill's verification reference on purpose: whether the grammar was the right choice, whether the content belongs on one diagram, whether a name means anything, whether a container's grouping is honest, whether the declared grouping is the real one, how much of its sweep a ring should fill, whether the layout matches the reader's mental order, a collision caused by a grown box when the render is not supplied, two connectors drawn on top of each other, and a color set as the element default rather than on the placement (the rule would report zero fills while EA draws them). A name colliding with a stereotype icon inside its own box cannot be caught by adding a rule, because the icon is not in the geometry. The inventory of defect classes lives with the server's benchmark suite and, as of 2026-09-30, records 22 classes: 21 detected across 18 rules and one that fires nothing. Re-run it rather than repeating the figure. Render the diagram and look: the checker is the floor, not the ceiling.
The fidelity comparison (fidelity.py)
What it does, and why it matters to you. It answers a different question from the diagram check: does the diagram the AI produced look like the one we were shown? The check judges a diagram against itself; fidelity judges it against a reference reading. It is for reproducing a diagram drawn in another tool when all that survives is an image. The two disagree and, on a reproduction, fidelity wins: a hand-placed reference has irregular gaps and widths that the check would correctly flag, and "fixing" them yields a tidier diagram that is no longer the one to reproduce. Gating a transcription on "clean" has gated on the wrong thing.
Logic: Rule-based.
What it takes in and produces
- A reading of the picture in image coordinates (vertical axis down): a required precision in pixels (there is no default and no tolerance constant, because every tolerance is derived from the reading's own declared precision), the elements (id, rectangle, optionally name, features, text and fill), optional links between elements, a color precision (required when any fill is declared), and the categories deliberately not read.
- What was drawn, in EA coordinates, each object declaring the reference element it stands for. Correspondence is declared, never guessed from names; name matching is only a fallback and the report says when it was used.
- A report with findings, metrics, what was not run, how the drawing fitted the reference, which elements matched, and whether it is "faithful" (no errors; named for what it claims because "clean" means something else one import away).
Used by. ea-diagram-composition, for reproducing a diagram from a picture.
How it behaves. Similarity, not equality: a uniform scale and offset between the two is fitted and absorbed (and reported), and what is left is judged. The fit is robust: the worst outlier is dropped and the fit retaken while any element exceeds the precision, down to half the elements, so one displaced box does not drag the frame and blame the innocent ones (who was left out is listed; every element is still measured). Rotation is not fitted. Produced rectangles are validated before conversion: a positive top or bottom means they are already in image coordinates and negating would mirror silently, so it raises. A partial reading must not be partial silently: what was not read and every skipped check come out on the not-run list. Fewer than two matched elements means no geometry comparison.
What it catches, grouped
- Missing or extra elements. A reference element nothing stands for (error); a produced object standing for nothing, or for an id already claimed (error).
- Position and order. An above, below, left or right relationship reversed, for pairs the reading separates by more than its precision (error); an element away from where the reference puts it after the fit (warning, error beyond three times the precision).
- Size and proportion. Width or height differing at the fitted scale (warning, error beyond three times the tolerance, which is twice the precision since two edges are read); the two axes needing different scales beyond what the precision explains (error).
- Links. The reference joins two elements and the reproduction does not (error); a connector the reference does not have (error); the same pair joined the other way round (warning).
- Content and color. A box shows a different number of attributes or operations than stated (warning); a box whose reading says its visible content is text is drawn empty (error; a Note draws its documentation, not its name); a fill differing in any channel (warning, error beyond three times the color precision).
Limitations. Judges placement, sizing, ordering, links, feature counts, text presence and fill. It does not compare wording (reproductions deliberately use reference content, not a copy of another model), rotation, line style or icon artwork. A reading that declares a precision it cannot support is the one way to get a wrong answer, and that is a property of the reading.
The binding measurement tool (measure_binding.py)
What it does, and why it matters to you. It is the authoring tool for bindings. It mines the element sizes and the gap between neighbors that a binding needs from a Sparx-authored model (Sparx's own example model), so each new binding starts from a measurement with a sample size attached instead of an estimate. It is a developer tool, not part of a diagram-composition run, and you will only meet it if you author a binding for a notation of your own.
Logic: Rule-based statistics; a person still decides what goes into the binding.
What it takes in and produces. A command line and a library. It takes a model path (default the example model in the EA install folder), a technology id (or UML for the base notation) and an optional diagram type, and options for how to treat furniture elements (Note, Text, Boundary), which base types to exclude, which concepts count as gap endpoints, and a stricter or historical measurement rule. It produces a draft binding block with every number annotated with its sample size. Anything with too few observations, no unique mode, no dominant size or a concentrated gap population is emitted as a comment, not a value.
Used by. Nothing at run time. It is how the shipped bindings were authored.
How it behaves.
- Hermetic. The model is copied to a private temporary directory and the copy is opened read-only. The original is never opened or written, EA is never asked anything, and there is no automation. If a journal sidecar sits next to the model (EA has it open), it refuses rather than measuring a torn file.
- EA's vertical axis. Width is right minus left and height is top minus bottom; "A above B" means A's bottom edge is a larger number than B's top edge.
- Technology and diagram type are data. A diagram's style carries
MDGDgm=<Technology>::<DiagramType>, and the key is present but empty on most diagrams. Attribution therefore has two paths, and the result says which each diagram took: the explicit tag, or, when the tag is empty, a base type used by that technology's tagged diagrams plus at least half of the stereotyped elements carrying a stereotype exclusive to that technology. - Sizes. The modal size per stereotype with its count, share and sample size (reported absent on a tie). The default is the pooled modal size of elements whose size is a choice; stereotypes fixed by the notation (at least 10 instances, at least 90% one size) are excluded from that pool.
- Gaps. Measured between an element and its nearest neighbor in a direction, inside the same set of containers, positive gaps only, endpoints stereotyped and not a Note, Text or Boundary. Reported as median and mode, each with its sample size.
- Guards. A minimum sample of 10 (a modal size reaches 95% agreement with the full population by about 8 to 9 observations); a minimum of 38 for a gap median (about four times as many, and a thin median is annotated rather than suppressed); a gap statistic is "concentrated" and not stated when one diagram supplies more than half of its population; furniture is promoted to content only when it is the majority type on a diagram and carries a stereotype the technology owns.
- Base notation (UML). Selects diagrams that claim no technology and whose non-furniture elements are at least half unstereotyped, keys by element type (94% of plain-UML elements have no stereotype), and drops documentation pages (more than half furniture, by aggregate). The drop costs real content on heavily annotated diagrams, and the output states the cost.
Limitations. Needs a Sparx-authored model that actually contains the technology's diagrams; a technology absent from it cannot be measured. The strict rule keeps cross-container spacing out of the population (it is a different quantity). Medians drawn from a population dominated by one diagram are not stated. The measured population is Sparx's example model, so a binding's numbers reflect how that model draws the notation.