Skip to content

[❓ Question]: Proposal: Universal Check Interface #883

@egarciamendez

Description

@egarciamendez

Preliminary Checks

  • I have read the project documentation.
  • I have searched the issue tracker for similar questions.

What Have You Tried?

Proposal: Universal Check Interface

I opened this issue to propose a minimal universal interface for structural checks in Blueprints.
My goal is to gather feedback from the community and reach consensus on the best approach before implementing it.

Sooooo @Blueprints-org/maintainers , let me know what you think!

Problem

At Blueprints we want to support structural checks that engineers can use for a broad variety of cases.
Think of checks like, shear capacity of a concrete beam, bending capacity of a steel section, or bearing capacity of a soil foundation.
Those are just examples, but the goal is to support any engineering check for any material (concrete, steel, soil, etc.) and any complexity level (from simple single checks to complex hierarchical checks composed of sub-checks).

So how are we going to achieve that in a clean, maintainable way and offer our users a consistent experience?

The short answer is you can't. Checks vary widely in their logic, inputs, outputs, and complexity.
However, we can define a minimal universal interface that all checks must implement. This interface will provide a consistent way to interact with any check, regardless of its internal workings.

In that way, users can rely on a common set of methods and properties to work with checks, while the specific implementation details can vary as needed.

Solution: Minimal Universal Interface

class StructuralCheckProtocol(Protocol):
    """Minimal interface for ANY engineering check."""

    name: str  # Check name (instance parameter)
    source_docs: list[str]  # Source references

    def execute(self, **kwargs) -> Any:  # Internal calculations
        """Flexible return type for check-specific logic."""

    def check(self) -> CheckResult:  # Public API
        """Execute check. Always returns CheckResult."""

    def calculation_steps(self) -> dict[str, "StructuralCheckProtocol"]:
        """Return sub-check instances (not results)."""

That's the complete interface. No more, no less.

Design Principles

  • Single Responsibility: Each element has one reason to exist

    • name: Identity
    • source_docs: Traceability
    • execute(): Internal logic
    • check(): Verification
    • calculation_steps(): Composition
  • Open/Closed: Add new checks without modifying existing code

  • Liskov Substitution: Any check works anywhere a check is expected

  • Interface Segregation: Only what's needed, nothing extra

  • Dependency Inversion: Depend on StructuralCheckProtocol, not implementations

Simple Check Pattern

@dataclass(frozen=True)
class SimpleShearCheck:
    """Minimal implementation."""

    name: str
    source_docs: list[str]
    v_ed: float
    v_rd: float

    def execute(self) -> float:
        return self.v_rd

    def check(self) -> CheckResult:
        return CheckResult.from_comparison(self.v_ed, self.v_rd)

    def calculation_steps(self) -> dict:
        return {}  # No sub-checks
    
    # ... and any other relevant methods that are specific to this check


# Usage
check = SimpleShearCheck(
    name="Shear check for Beam B-1",
    source_docs=["EN 1992-1-1 art. 6.2.2"],
    v_ed=150_000,
    v_rd=200_000,
)
result = check.check()

Composite Check Pattern

@dataclass(frozen=True)
class SteelBeamCheck:
    """Check composed of sub-checks."""

    name: str
    source_docs: list[str]
    m_ed: float
    m_rd: float
    v_ed: float
    v_rd: float

    def execute(self) -> dict[str, BaseCheck]:
        """Create sub-check instances."""
        bending = BendingCheck("Bending", ["EN 1993"], self.m_ed, self.m_rd)
        shear = ShearCheck("Shear", ["EN 1993"], self.v_ed, self.v_rd)
        return {"bending": bending, "shear": shear}

    def check(self) -> CheckResult:
        """Aggregate sub-checks."""
        checks = self.execute()
        return aggregate_checks(checks, mode="all_must_pass")

    def calculation_steps(self) -> dict[str, StructuralCheckProtocol]:
        """Return sub-check instances."""
        return self.execute()
    
    # ... and any other relevant methods that are specific to this check


# Usage
check = SteelBeamCheck(
    name="Steel IPE 300",
    source_docs=["EN 1993-1-1:2005"],
    m_ed=100_000,
    m_rd=150_000,
    v_ed=50_000,
    v_rd=75_000,
)

# Overall result
overall = check.check()

# Inspect sub-checks independently
for name, sub_check in check.calculation_steps().items():
    result = sub_check.check()
    print(f"{name}: {result.is_ok}")

OPTIONAL: BaseCheck ABC

For the case where inheritance is preferred over duck typing:

@dataclass(frozen=True)
class BaseCheck(ABC):
    """Optional base class. Implements StructuralCheckProtocol."""

    name: str
    source_docs: list[str] = field(default_factory=list)

    @abstractmethod
    def execute(self, **kwargs) -> Any:
        pass

    @abstractmethod
    def check(self) -> CheckResult:
        pass
    
    @abstractmethod
    def calculation_steps(self) -> dict[str, "StructuralCheckProtocol"]:
        return {}  # Default: no sub-checks
    
    @abstractmethod
    def report(self) -> LatexReport:
        """Optional: Generate formatted reports."""
        return LatexReport(...)

Choice: Use Protocol (duck typing) OR BaseCheck (structure). Both work identically.

Summary

A minimal, universal interface for structural checks that:

  • Scales from simple single-checks to complex hierarchies
  • Works for any material (concrete, steel, timber, masonry, etc.)
  • Follows SOLID principles
  • Maintains backward compatibility
  • Provides optional convenience features (BaseCheck, report(), aggregation)

The interface requires only 5 elements:

  1. name - Check identity
  2. source_docs - Documentation references
  3. execute() - Internal calculations
  4. check() - Public verification API
  5. calculation_steps() - Sub-check composition

So what do you think? Is this the right approach for Blueprints?

  1. Should calculation_steps() use a different name? (sub_checks(), components(), children()?)
  2. Any material-specific patterns needed?
  3. Other suggestions or improvements?
  4. ABC or Protocol / Maybe both?

Question Description

I would love to have some feedback on this before starting on the implementation.

Relevant Code or Steps

No response

Confirmation

  • I have checked that this question does not already exist in the issue tracker or documentation.
  • I have read and understood the contribution guidelines.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions