Skip to content

Adds parsing / validation of JSON configuration, for I19 transmission hardware. - #2173

Open
CoePaul wants to merge 2 commits into
mainfrom
issue2172
Open

Adds parsing / validation of JSON configuration, for I19 transmission hardware.#2173
CoePaul wants to merge 2 commits into
mainfrom
issue2172

Conversation

@CoePaul

@CoePaul CoePaul commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Contributes to #2172

Instructions to reviewer on how to test:

  1. Check BaseModel classes against JSON in file mentioned from i19 config in Pydantic BaseModels to capture I19 transmission system JSON specifications #2172 description
  2. Check tests cover reasonable expectations for JSON validation
  3. Ensure CI passes the tests and that the python matches house style ( etc )

N.B. Voids in an i19 absorber wedge are bubbles in the absorber material ( 3-D printer output )
and therefore motor positions to avoid:
Nothing to do with the software void type.

Checks for reviewer

  • Would the PR title make sense to a scientist on a set of release notes
  • If a new device has been added does it follow the standards
  • If changing the API for a pre-existing device, ensure that any beamlines using this device have updated their Bluesky plans accordingly
  • Have the connection tests for the relevant beamline(s) been run via dodal connect ${BEAMLINE}

@CoePaul
CoePaul requested a review from a team as a code owner August 13, 2026 15:10
@CoePaul CoePaul added the i19-2 label Aug 13, 2026
@CoePaul CoePaul self-assigned this Aug 13, 2026
@CoePaul
CoePaul marked this pull request as draft August 13, 2026 15:11
@CoePaul CoePaul added i19-1 python Pull requests that update Python code labels Aug 13, 2026
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.35%. Comparing base (e8b29c8) to head (04b22c3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2173      +/-   ##
==========================================
+ Coverage   99.33%   99.35%   +0.01%     
==========================================
  Files         367      378      +11     
  Lines       14379    14623     +244     
==========================================
+ Hits        14284    14528     +244     
  Misses         95       95              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CoePaul
CoePaul force-pushed the issue2172 branch 4 times, most recently from 4cf081e to 13c130e Compare August 19, 2026 20:26
@CoePaul CoePaul changed the title Issue2172 Adds parsing / validation of JSON configuration, for I19 transmission hardware. Aug 27, 2026
@CoePaul
CoePaul marked this pull request as ready for review August 27, 2026 15:17
@CoePaul
CoePaul force-pushed the issue2172 branch 4 times, most recently from 736f148 to 9da488d Compare August 28, 2026 00:05

@DominicOram DominicOram left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I think the main question is around why we have SystemAspectBaseParser when we could use standard pydantic tools instead?

Additionally, are the wedges mounted on the lateral_motors? In which case why are they separate things in the json structure? Why not have something like:

  "wedges": {
    "y": {
      "material" : "aluminium",
      "geometry": {
        "taper_cotangent" : 9.3985,
        "tip" : 5.06,
        "voids": []
        "motion": {
          "units": "mm",
          "out" : 5.0,
          "threshold": 8.9,
          "max": 98.0,
          "tolerance": 5.0e-3
        }
      }
    },
}

This would remove a bunch of edge cases about one not being defined when the other is or the names not matching

I haven't looked at the tests yet, I think once we resolve the discussion on SystemAspectBaseParser they might change a bit anyway.

upper: The upper end of the energy interval (range) for specific absorption fit curve.
"""

units: str = Field(..., pattern=r"^(keV|kiloelectronvolts)$")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should:

Suggested change
units: str = Field(..., pattern=r"^(keV|kiloelectronvolts)$")
units: Literal["keV", "kiloelectronvolts"]

is more concise and readable. It also means the type system will enforce it later if we ever use the value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know You could enforce a choice of two strings by means of a Literal list...

Optional residuals correction, zeroth order parameter first.
"""

photon_absorption: StrictFloat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: As above,

Suggested change
photon_absorption: StrictFloat
photon_absorption: StrictFloat= Field(gt=0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it probably makes sense

"""

photon_absorption: StrictFloat
roll_off: StrictFloat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could:

Suggested change
roll_off: StrictFloat
roll_off: StrictFloat = Field(lt=-2, gt=-4)

(probably with -2 and -4 pulled into constants). Let's pydantic do all the checking for you. With this and above you can remove the validate_attributes entirely

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a) I wasn't aware Field would work with constants - although it seems reasonable that it can

b) there's no good name for the bounds as they are arbitrary and probably too wide and too "3-centric"
I just don't want +2.67 to be accepted
and accepting -4.2 would imply accepting a typo

but yes it could be better explained - how it is somewhat vague and that vagueness is reflected in the code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could just be named something like REALISTIC_PHYSICAL_LOWER_BOUND?

Comment on lines +42 to +43
f"Absorption roll off {self.roll_off} does not seem likely on physics grounds."
if self.roll_off < 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: Why have a separate check on the positive case? Then have a cryptic message that doesn't tell the user how to fix the issue? I think better to remove this case and just check that it's around -3

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look again - I'm sure bits of it made sense as I was tweaking it

cls._VALIDATOR.validate_name(name_to_check=axis_name)


class MaterialNameValidation:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: Why does it matter what someone wants to name their material?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't - but there's every advantage in picking up typos from edits to JSON.
You can't catch them all - but does that really mean You should catch no categories at all?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just don't want material 1234_iron. - Why would I allow ints at the start?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, can we at least add a comment to the affect that it's actually just convention/preference? Otherwise I worry that someone will ask for it in 5 years for a legitimate reason and we'll be worried we can't change it as it might break something

Comment on lines +73 to +78
- Each absorber wedge has a motor to drive it sideways across the x-ray beam:
- expected sideways motions are pure horizontal or pure vertical
- but that's a detail, azimuthal orientation around the beam should not matter.
- The wedge motor scale has
- an **out** position
- and then a range of "active" positions, ( where the mathematical assumption of a linear taper is reasonable ).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: None of this is actually matched by anything in the Spec so it's not clear why it's here and I need to think about it at this point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notes are "You can read this - but You don't have to"

that's why it's a note.
I depends whether the poor developer reading this is trying to understand the whole system or not.

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it from the "I understand the transmission system" end of the telescope - the reader WILL be wondering where's the "move to the absorber OUT the way" gubbins?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every attenuating element has a "range" of positions which are absorbing
( sometimes - ie for a foil, range = 1 place only )
and a singular location for OUT
( in principle - even if not in practice - lateral motor +/- margin of readout error is roughly a singular location )

OUT is an almost canonical addendum to the "range" of permitted positions for an absorber.

It's a very inhomogenous logical smush together of range + OUT but I couldn't see any way round it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it from the "I understand the transmission system" end of the telescope - the reader WILL be wondering where's the "move to the absorber OUT the way" gubbins?

Yes, exactly and they're not here. Ok, so happy to leave the description here if we have a note to see LateralMotorSpec?

permissions: Those slot numbers which are permitted to be in use.
"""

foils: dict[str, FoilSpec]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: These are always numbers, right?

Suggested change
foils: dict[str, FoilSpec]
foils: dict[int, FoilSpec]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON keys are only strings - I didn't invent JSON

Image Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so no - they are never integers and always "integers"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the JSON dict key must be a string but the python dict key doesn't have to be so you could do the conversion here to enforce it to an integer and stop the user putting something in that isn't an integer

raise ValueError(_msg)


class WheelsConfig(SystemAspectBaseParser[WheelSpec]):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must: As above re SystemAspectBaseParser

@DominicOram DominicOram left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, meant the above as a request changes, not a comment

* Start with adding JSON validation for system specification
  JSON dict-like structure expectations

* Pydantic BaseModel classes map sub-structures ( sub-dicts ) within
  the system description structure here adding
   JSON validation for energy_interval
   [ the range of x-ray energies (in keV ) over which an absorption curve is valid ]
   JSON validation for absorption fit curve parameters
   [ the scaling constant, roll-off vs energy and an optional residuals polynomial ]
* Use pydantic BaseModel classes to capture blobs from Transmission System
  specification config JSON files for I19 beamlines EH-1 / EH-2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

i19-1 i19-2 python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants