///|
fn formula_property_value(
sets : Array[PropertySet],
formula : QuantityFormula,
) -> Double? {
for name in formula.property_names {
match property_number(sets, name) {
Some(value) => return Some(value * formula.multiplier)
None => ()
}
}
None
}
///|
fn formula_base_value(row : ProductTakeoff, kind : QuantityKind) -> Double? {
match row.quantity(kind) {
Some(value) => Some(value.value)
None => None
}
}
///|
fn formula_value_for_row(
index : ModelIndex,
row : ProductTakeoff,
formula : QuantityFormula,
) -> (QuantityValue, QuantityNote?) raise ParseError {
let sets = property_sets_for_product(index, row.product)
match formula_property_value(sets, formula) {
Some(value) =>
(
quantity_value(formula.kind, value, "property", false, formula.unit),
None,
)
None =>
if formula.fallback_to_geometry {
match formula_base_value(row, formula.kind) {
Some(value) =>
(
quantity_value(
formula.kind,
value,
"geometry-fallback",
true,
formula.unit,
),
Some(
quantity_note(
row.product.id,
formula.kind,
"property formula not populated; geometry fallback used",
),
),
)
None =>
(
quantity_value(formula.kind, 0.0, "missing", true, formula.unit),
Some(
quantity_note(
row.product.id,
formula.kind,
"property formula and geometry quantity are missing",
severity=Warning,
),
),
)
}
} else {
(
quantity_value(formula.kind, 0.0, "missing", true, formula.unit),
Some(
quantity_note(
row.product.id,
formula.kind,
"property formula is not populated",
severity=Warning,
),
),
)
}
}
}
///|
fn formula_replace_quantity(
values : Array[QuantityValue],
replacement : QuantityValue,
) -> Array[QuantityValue] {
let result = values.copy()
for index in 0.. FormulaTakeoff raise ParseError {
let index = model.index()
let base = quantity_takeoff(
model,
options=default_takeoff_options().including_empty(true),
)
let rows : Array[ProductTakeoff] = []
let notes : Array[QuantityNote] = []
let mut missing = 0
for row in base.products {
let mut quantities = row.quantities
for formula in formulas {
let (value, note) = formula_value_for_row(index, row, formula)
quantities = formula_replace_quantity(quantities, value)
match note {
Some(item) => {
notes.push(item)
missing += 1
}
None => ()
}
}
rows.push({ ..row, quantities, })
}
{
rows,
notes,
formulas,
product_count: rows.length(),
missing_formula_count: missing,
}
}
///|
pub fn FormulaTakeoff::row_count(self : FormulaTakeoff) -> Int {
self.rows.length()
}
///|
pub fn FormulaTakeoff::find(
self : FormulaTakeoff,
product_id : Int,
) -> ProductTakeoff? {
for row in self.rows {
if row.product.id == product_id {
return Some(row)
}
}
None
}
///|
pub fn FormulaTakeoff::quantity(
self : FormulaTakeoff,
product_id : Int,
kind : QuantityKind,
) -> QuantityValue? {
match self.find(product_id) {
Some(row) => row.quantity(kind)
None => None
}
}
///|
pub fn FormulaTakeoff::note_count(self : FormulaTakeoff) -> Int {
self.notes.length()
}
///|
pub fn FormulaTakeoff::warnings(self : FormulaTakeoff) -> Array[QuantityNote] {
self.notes.filter(note => note.severity == Warning)
}
///|
pub fn FormulaTakeoff::total(
self : FormulaTakeoff,
kind : QuantityKind,
) -> Double {
self.rows.fold(init=0.0, (result, row) => result + row.value(kind))
}
///|
pub fn FormulaTakeoff::headline(self : FormulaTakeoff) -> String {
"rows=" +
self.rows.length().to_string() +
" formulas=" +
self.formulas.length().to_string() +
" notes=" +
self.notes.length().to_string() +
" missing=" +
self.missing_formula_count.to_string()
}
///|
pub fn FormulaTakeoff::formula_names(self : FormulaTakeoff) -> Array[String] {
self.formulas.map(formula => quantity_kind_name(formula.kind))
}
///|
pub fn FormulaTakeoff::to_json(self : FormulaTakeoff) -> String {
let rows = self.rows.map(row => row.to_json()).join(",")
let notes = self.notes
.map(note => {
"{\"product_id\":" +
note.product_id.to_string() +
",\"kind\":\"" +
quantity_kind_name(note.kind) +
"\",\"message\":\"" +
note.message +
"\"}"
})
.join(",")
"{\"product_count\":" +
self.product_count.to_string() +
",\"missing_formula_count\":" +
self.missing_formula_count.to_string() +
",\"rows\":[" +
rows +
"],\"notes\":[" +
notes +
"]}"
}
///|
pub fn FormulaTakeoff::to_csv(self : FormulaTakeoff) -> String {
let builder = StringBuilder()
builder <+ "product_id,type,name"
for formula in self.formulas {
builder <+ ",\{quantity_kind_name(formula.kind)}"
}
builder <+ "\n"
for row in self.rows {
let name = row.product.name.unwrap_or("")
builder <+ "\{row.product.id},\{row.product.ifc_type},\{name}"
for formula in self.formulas {
builder <+ ",\{row.value(formula.kind)}"
}
builder <+ "\n"
}
builder.to_string()
}
///|
pub fn FormulaTakeoff::to_markdown(self : FormulaTakeoff) -> String {
let builder = StringBuilder()
builder <+ "# Formula takeoff\n\n"
builder <+ "- Rows: \{self.rows.length()}\n"
builder <+ "- Formulas: \{self.formulas.length()}\n"
builder <+ "- Notes: \{self.notes.length()}\n\n"
for note in self.notes {
builder <+ "- \{note.headline()}\n"
}
builder.to_string()
}
///|
pub fn formula_takeoff_for_kind(
model : Model,
kind : ProductKind,
formulas : Array[QuantityFormula],
) -> FormulaTakeoff raise ParseError {
let takeoff = formula_takeoff(model, formulas)
{ ..takeoff, rows: takeoff.rows.filter(row => row.product.kind == kind) }
}
///|
pub fn formula_quantity(
model : Model,
product_id : Int,
formula : QuantityFormula,
) -> QuantityValue? raise ParseError {
formula_takeoff(model, [formula]).quantity(product_id, formula.kind)
}