use super::*; use crate::analysis_numeric::{NumericType, ResolvedKind, Unit}; use crate::{Id, Project, SharedBody}; use std::collections::BTreeMap; /// Bounded local nominal origin; no rooted graph is cloned during arithmetic. /// Full ResolvedKind equality additionally includes dimension/algebra/difference. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct KindHandle { model: Id, definition: Id, } impl KindHandle { pub fn definition(self) -> Id { self.definition } pub(crate) fn local(model: Id, definition: Id) -> Self { Self { model, definition } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResolvedScalar { Boolean, Numeric(NumericType), } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResolvedType { Scalar(ResolvedScalar), List(ResolvedScalar), } impl ResolvedType { pub(crate) fn boolean() -> Self { Self::Scalar(ResolvedScalar::Boolean) } pub(crate) fn numeric(value: NumericType) -> Self { Self::Scalar(ResolvedScalar::Numeric(value.normalized())) } } #[derive(Debug, Clone, Copy)] pub struct EffectiveBinding<'a> { pub binding: &'a Binding, pub origin: BindingOrigin, } /// Constructed only by admission; exact source/capture provenance stays with its owner. /// Values from different accepted environments must not be mixed by an evaluator. pub struct ResolvedAnalysis<'p> { pub(super) project: &'p Project, pub(super) kinds: BTreeMap>, pub(super) units: BTreeMap>, pub(super) limits: AdmissionLimits, pub(super) usage: AdmissionUsage, } impl<'p> ResolvedAnalysis<'p> { pub fn project(&self) -> &'p Project { self.project } pub fn admission_usage(&self) -> AdmissionUsage { self.usage } pub fn admission_limits(&self) -> AdmissionLimits { self.limits } pub fn definition(&self, reference: DefinitionRef) -> Option<&'p Definition> { self.project.definitions.get(&reference.local()) } pub fn kind(&self, reference: DefinitionRef) -> Option<&ResolvedKind> { self.kinds.get(&reference.local()) } pub fn unit(&self, reference: DefinitionRef) -> Option<&Unit> { self.units.get(&reference.local()) } pub fn calculation(&self, reference: DefinitionRef) -> Option<&'p CalculationDefinition> { match &self.definition(reference)?.body { DefinitionBody::Calculation(value) => Some(value), _ => None, } } pub fn declared_type(&self, value: &AnalysisType) -> Option { let (AnalysisType::Scalar(scalar) | AnalysisType::List(scalar)) = value; let scalar = match scalar { ScalarType::Boolean => ResolvedScalar::Boolean, ScalarType::Number => ResolvedScalar::Numeric(NumericType::Number), ScalarType::Quantity(reference) => { ResolvedScalar::Numeric(NumericType::Named(self.kind(*reference)?.clone())) } }; Some(match value { AnalysisType::Scalar(_) => ResolvedType::Scalar(scalar), AnalysisType::List(_) => ResolvedType::List(scalar), }) } pub fn effective_binding( &self, property: Id, context: EvaluationContext, ) -> Result, AnalysisError> { let element = self.project.elements.get(&property).ok_or_else(|| { AnalysisError::new( property, "context", "analysis_property", "Property does not exist.", ) })?; let Some(SharedBody::EngineeringProperty { base, .. }) = element.shared() else { return Err(AnalysisError::new( property, "context", "analysis_property", "Target is not an engineering property.", )); }; if let EvaluationContext::Case(id) = context { let case = self.project.elements.get(&id).ok_or_else(|| { AnalysisError::new(id, "context", "analysis_case", "Case does not exist.") })?; let Some(SharedBody::AnalysisCase { overrides, .. }) = case.shared() else { return Err(AnalysisError::new( id, "context", "analysis_case", "Context is not an analysis case.", )); }; if case.layer() != element.layer() { return Err(AnalysisError::new( id, "context", "analysis_layer", "Case and property must share a layer.", )); } if let Some(binding) = overrides.get(&property) { return Ok(EffectiveBinding { binding, origin: BindingOrigin::CaseOverride(id), }); } } Ok(EffectiveBinding { binding: base, origin: BindingOrigin::ModelBaseline, }) } }