Functional coverage tree

From Rosetta Code
Task
Functional coverage tree
You are encouraged to solve this task according to the task description, using any language you may know.

Functional coverage is a measure of how much a particular function of a system has been verified as correct. It is used heavily in tracking the completeness of the verification of complex System on Chip (SoC) integrated circuits, where it can also be used to track how well the functional requirements of the system have been verified.

This task uses a sub-set of the calculations sometimes used in tracking functional coverage but uses a more familiar(?) scenario.

Task Description

The head of the clean-up crews for "The Men in a very dark shade of grey when viewed at night" has been tasked with managing the cleansing of two properties after an incident involving aliens.

She arranges the task hierarchically with a manager for the crews working on each house who return with a breakdown of how they will report on progress in each house.

The overall hierarchy of (sub)tasks is as follows,

cleaning
    house1
        bedrooms
        bathrooms
            bathroom1
            bathroom2
            outside lavatory
        attic
        kitchen
        living rooms
            lounge
            dining room
            conservatory
            playroom
        basement
        garage
        garden
    house2
        upstairs
            bedrooms
                suite 1
                suite 2
                bedroom 3
                bedroom 4
            bathroom
            toilet
            attics
        groundfloor
            kitchen
            living rooms
                lounge
                dining room
                conservatory
                playroom
            wet room & toilet
            garage
            garden
            hot tub suite
        basement
            cellars
            wine cellar
            cinema

The head of cleanup knows that her managers will report fractional completion of leaf tasks (tasks with no child tasks of their own), and she knows that she will want to modify the weight of values of completion as she sees fit.

Some time into the cleaning, and some coverage reports have come in and she thinks see needs to weight the big house2 60-40 with respect to coverage from house1 She prefers a tabular view of her data where missing weights are assumed to be 1.0 and missing coverage 0.0.

NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |        |          |
    house1                      |40      |          |
        bedrooms                |        |0.25      |
        bathrooms               |        |          |
            bathroom1           |        |0.5       |
            bathroom2           |        |          |
            outside_lavatory    |        |1         |
        attic                   |        |0.75      |
        kitchen                 |        |0.1       |
        living_rooms            |        |          |
            lounge              |        |          |
            dining_room         |        |          |
            conservatory        |        |          |
            playroom            |        |1         |
        basement                |        |          |
        garage                  |        |          |
        garden                  |        |0.8       |
    house2                      |60      |          |
        upstairs                |        |          |
            bedrooms            |        |          |
                suite_1         |        |          |
                suite_2         |        |          |
                bedroom_3       |        |          |
                bedroom_4       |        |          |
            bathroom            |        |          |
            toilet              |        |          |
            attics              |        |0.6       |
        groundfloor             |        |          |
            kitchen             |        |          |
            living_rooms        |        |          |
                lounge          |        |          |
                dining_room     |        |          |
                conservatory    |        |          |
                playroom        |        |          |
            wet_room_&_toilet   |        |          |
            garage              |        |          |
            garden              |        |0.9       |
            hot_tub_suite       |        |1         |
        basement                |        |          |
            cellars             |        |1         |
            wine_cellar         |        |1         |
            cinema              |        |0.75      |
Calculation

The coverage of a node in the tree is calculated as the weighted average of the coverage of its children evaluated bottom-upwards in the tree.

The task is to calculate the overall coverage of the cleaning task and display the coverage at all levels of the hierarchy on this page, in a manner that visually shows the hierarchy, weights and coverage of all nodes.

Extra Credit

After calculating the coverage for all nodes, one can also calculate the additional/delta top level coverage that would occur if any (sub)task were to be fully covered from its current fractional coverage. This is done by multiplying the extra coverage that could be gained for any node, by the product of the `powers` of its parent nodes from the top down to the node.
The power of a direct child of any parent is given by the power of the parent multiplied by the weight of the child divided by the sum of the weights of all the direct children.

The pseudo code would be:

   method delta_calculation(this, power):
       sum_of_weights = sum(node.weight for node in children)
       this.delta  = (1 - this.coverage) * power
       for node in self.children:
           node.delta_calculation(power * node.weight / sum_of_weights)
       return this.delta

Followed by a call to:

   top.delta_calculation(power=1)


Note: to aid in getting the data into your program you might want to use an alternative, more functional description of the starting data given on the discussion page.

Go

Translation of: Kotlin
package main

import "fmt"

type FCNode struct {
    name     string
    weight   int
    coverage float64
    children []*FCNode
    parent   *FCNode
}

func newFCN(name string, weight int, coverage float64) *FCNode {
    return &FCNode{name, weight, coverage, nil, nil}
}

func (n *FCNode) addChildren(nodes []*FCNode) {
    for _, node := range nodes {
        node.parent = n
        n.children = append(n.children, node)
    }
    n.updateCoverage()
}

func (n *FCNode) setCoverage(value float64) {
    if n.coverage != value {
        n.coverage = value
        // update any parent's coverage
        if n.parent != nil {
            n.parent.updateCoverage()
        }
    }
}

func (n *FCNode) updateCoverage() {
    v1 := 0.0
    v2 := 0
    for _, node := range n.children {
        v1 += float64(node.weight) * node.coverage
        v2 += node.weight
    }
    n.setCoverage(v1 / float64(v2))
}

func (n *FCNode) show(level int) {
    indent := level * 4
    nl := len(n.name) + indent
    fmt.Printf("%*s%*s  %3d   | %8.6f |\n", nl, n.name, 32-nl, "|", n.weight, n.coverage)
    if len(n.children) == 0 {
        return
    }
    for _, child := range n.children {
        child.show(level + 1)
    }
}

var houses = []*FCNode{
    newFCN("house1", 40, 0),
    newFCN("house2", 60, 0),
}

var house1 = []*FCNode{
    newFCN("bedrooms", 1, 0.25),
    newFCN("bathrooms", 1, 0),
    newFCN("attic", 1, 0.75),
    newFCN("kitchen", 1, 0.1),
    newFCN("living_rooms", 1, 0),
    newFCN("basement", 1, 0),
    newFCN("garage", 1, 0),
    newFCN("garden", 1, 0.8),
}

var house2 = []*FCNode{
    newFCN("upstairs", 1, 0),
    newFCN("groundfloor", 1, 0),
    newFCN("basement", 1, 0),
}

var h1Bathrooms = []*FCNode{
    newFCN("bathroom1", 1, 0.5),
    newFCN("bathroom2", 1, 0),
    newFCN("outside_lavatory", 1, 1),
}

var h1LivingRooms = []*FCNode{
    newFCN("lounge", 1, 0),
    newFCN("dining_room", 1, 0),
    newFCN("conservatory", 1, 0),
    newFCN("playroom", 1, 1),
}

var h2Upstairs = []*FCNode{
    newFCN("bedrooms", 1, 0),
    newFCN("bathroom", 1, 0),
    newFCN("toilet", 1, 0),
    newFCN("attics", 1, 0.6),
}

var h2Groundfloor = []*FCNode{
    newFCN("kitchen", 1, 0),
    newFCN("living_rooms", 1, 0),
    newFCN("wet_room_&_toilet", 1, 0),
    newFCN("garage", 1, 0),
    newFCN("garden", 1, 0.9),
    newFCN("hot_tub_suite", 1, 1),
}

var h2Basement = []*FCNode{
    newFCN("cellars", 1, 1),
    newFCN("wine_cellar", 1, 1),
    newFCN("cinema", 1, 0.75),
}

var h2UpstairsBedrooms = []*FCNode{
    newFCN("suite_1", 1, 0),
    newFCN("suite_2", 1, 0),
    newFCN("bedroom_3", 1, 0),
    newFCN("bedroom_4", 1, 0),
}

var h2GroundfloorLivingRooms = []*FCNode{
    newFCN("lounge", 1, 0),
    newFCN("dining_room", 1, 0),
    newFCN("conservatory", 1, 0),
    newFCN("playroom", 1, 0),
}

func main() {
    cleaning := newFCN("cleaning", 1, 0)

    house1[1].addChildren(h1Bathrooms)
    house1[4].addChildren(h1LivingRooms)
    houses[0].addChildren(house1)

    h2Upstairs[0].addChildren(h2UpstairsBedrooms)
    house2[0].addChildren(h2Upstairs)
    h2Groundfloor[1].addChildren(h2GroundfloorLivingRooms)
    house2[1].addChildren(h2Groundfloor)
    house2[2].addChildren(h2Basement)
    houses[1].addChildren(house2)

    cleaning.addChildren(houses)
    topCoverage := cleaning.coverage
    fmt.Printf("TOP COVERAGE = %8.6f\n\n", topCoverage)
    fmt.Println("NAME HIERARCHY                 | WEIGHT | COVERAGE |")
    cleaning.show(0)

    h2Basement[2].setCoverage(1) // change Cinema node coverage to 1
    diff := cleaning.coverage - topCoverage
    fmt.Println("\nIf the coverage of the Cinema node were increased from 0.75 to 1")
    fmt.Print("the top level coverage would increase by ")
    fmt.Printf("%8.6f to %8.6f\n", diff, topCoverage+diff)
    h2Basement[2].setCoverage(0.75) // restore to original value if required
}
Output:
TOP COVERAGE = 0.409167

NAME HIERARCHY                 | WEIGHT | COVERAGE |
cleaning                       |    1   | 0.409167 |
    house1                     |   40   | 0.331250 |
        bedrooms               |    1   | 0.250000 |
        bathrooms              |    1   | 0.500000 |
            bathroom1          |    1   | 0.500000 |
            bathroom2          |    1   | 0.000000 |
            outside_lavatory   |    1   | 1.000000 |
        attic                  |    1   | 0.750000 |
        kitchen                |    1   | 0.100000 |
        living_rooms           |    1   | 0.250000 |
            lounge             |    1   | 0.000000 |
            dining_room        |    1   | 0.000000 |
            conservatory       |    1   | 0.000000 |
            playroom           |    1   | 1.000000 |
        basement               |    1   | 0.000000 |
        garage                 |    1   | 0.000000 |
        garden                 |    1   | 0.800000 |
    house2                     |   60   | 0.461111 |
        upstairs               |    1   | 0.150000 |
            bedrooms           |    1   | 0.000000 |
                suite_1        |    1   | 0.000000 |
                suite_2        |    1   | 0.000000 |
                bedroom_3      |    1   | 0.000000 |
                bedroom_4      |    1   | 0.000000 |
            bathroom           |    1   | 0.000000 |
            toilet             |    1   | 0.000000 |
            attics             |    1   | 0.600000 |
        groundfloor            |    1   | 0.316667 |
            kitchen            |    1   | 0.000000 |
            living_rooms       |    1   | 0.000000 |
                lounge         |    1   | 0.000000 |
                dining_room    |    1   | 0.000000 |
                conservatory   |    1   | 0.000000 |
                playroom       |    1   | 0.000000 |
            wet_room_&_toilet  |    1   | 0.000000 |
            garage             |    1   | 0.000000 |
            garden             |    1   | 0.900000 |
            hot_tub_suite      |    1   | 1.000000 |
        basement               |    1   | 0.916667 |
            cellars            |    1   | 1.000000 |
            wine_cellar        |    1   | 1.000000 |
            cinema             |    1   | 0.750000 |

If the coverage of the Cinema node were increased from 0.75 to 1
the top level coverage would increase by 0.016667 to 0.425833

Haskell

Using a function from a text outline to an updated text outline.

The raw table (supplied in the task description) is read in from a text file, parsed to a tree structure, and updated by two traversals (one bottom-up and one top down) before being serialised back to a completed outline text, with an additional 'Share of Residue' column:

Translation of: Python
{-# LANGUAGE OverloadedStrings #-}

import Data.Bifunctor (first)
import Data.Bool (bool)
import Data.Char (isSpace)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Read as T
import Data.Tree (Forest, Tree (..), foldTree)
import Numeric (showFFloat)
import System.Directory (doesFileExist)

----------------- FUNCTIONAL COVERAGE TREE ---------------

data Coverage = Coverage
  { name :: T.Text,
    weight :: Float,
    coverage :: Float,
    share :: Float
  }
  deriving (Show)

--------------------------- TEST -------------------------
fp = "./coverageOutline.txt"

main :: IO ()
main =
  doesFileExist fp
    >>= bool
      (print $ "File not found: " <> fp)
      (T.readFile fp >>= T.putStrLn . updatedCoverageOutline)

----------------- UPDATED COVERAGE OUTLINE ---------------
updatedCoverageOutline :: T.Text -> T.Text
updatedCoverageOutline s =
  let delimiter = "|"
      indentedLines = T.lines s
      columnNames =
        init $
          tokenizeWith
            delimiter
            ( head indentedLines
            )
   in T.unlines
        [ tabulation
            delimiter
            (columnNames <> ["SHARE OF RESIDUE"]),
          indentedLinesFromTree
            "    "
            (showCoverage delimiter)
            $ withResidueShares 1.0 $
              foldTree
                weightedCoverage
                (parseTreeFromOutline delimiter indentedLines)
        ]

------ WEIGHTED COVERAGE AND SHARES OF REMAINING WORK ----
weightedCoverage ::
  Coverage ->
  Forest Coverage ->
  Tree Coverage
weightedCoverage x xs =
  let cws = ((,) . coverage <*> weight) . rootLabel <$> xs
      totalWeight = foldr ((+) . snd) 0 cws
   in Node
        ( x
            { coverage =
                foldr
                  (\(c, w) a -> (c * w) + a)
                  (coverage x)
                  cws
                  / bool 1 totalWeight (0 < totalWeight)
            }
        )
        xs

withResidueShares :: Float -> Tree Coverage -> Tree Coverage
withResidueShares shareOfTotal tree =
  let go fraction node =
        let forest = subForest node
            weights = weight . rootLabel <$> forest
            weightTotal = sum weights
            nodeRoot = rootLabel node
         in Node
              ( nodeRoot
                  { share =
                      fraction
                        * (1 - coverage nodeRoot)
                  }
              )
              ( zipWith
                  go
                  ((fraction *) . (/ weightTotal) <$> weights)
                  forest
              )
   in go shareOfTotal tree

---------------------- OUTLINE PARSE ---------------------
parseTreeFromOutline :: T.Text -> [T.Text] -> Tree Coverage
parseTreeFromOutline delimiter indentedLines =
  partialRecord . tokenizeWith delimiter
    <$> head
      ( forestFromLineIndents $
          indentLevelsFromLines $ tail indentedLines
      )

forestFromLineIndents :: [(Int, T.Text)] -> [Tree T.Text]
forestFromLineIndents pairs =
  let go [] = []
      go ((n, s) : xs) =
        let (firstTreeLines, rest) = span ((n <) . fst) xs
         in Node s (go firstTreeLines) : go rest
   in go pairs

indentLevelsFromLines :: [T.Text] -> [(Int, T.Text)]
indentLevelsFromLines xs =
  let pairs = T.span isSpace <$> xs
      indentUnit =
        foldr
          ( \x a ->
              let w = (T.length . fst) x
               in bool a w (w < a && 0 < w)
          )
          (maxBound :: Int)
          pairs
   in first (flip div indentUnit . T.length) <$> pairs

partialRecord :: [T.Text] -> Coverage
partialRecord xs =
  let [name, weightText, coverageText] =
        take
          3
          (xs <> repeat "")
   in Coverage
        { name = name,
          weight = defaultOrRead 1.0 weightText,
          coverage = defaultOrRead 0.0 coverageText,
          share = 0.0
        }

defaultOrRead :: Float -> T.Text -> Float
defaultOrRead n txt = either (const n) fst $ T.rational txt

tokenizeWith :: T.Text -> T.Text -> [T.Text]
tokenizeWith delimiter = fmap T.strip . T.splitOn delimiter

-------- SERIALISATION OF TREE TO TABULATED OUTLINE ------
indentedLinesFromTree ::
  T.Text ->
  (T.Text -> a -> T.Text) ->
  Tree a ->
  T.Text
indentedLinesFromTree tab showRoot tree =
  let go indent node =
        showRoot indent (rootLabel node) :
        (subForest node >>= go (T.append tab indent))
   in T.unlines $ go "" tree

showCoverage :: T.Text -> T.Text -> Coverage -> T.Text
showCoverage delimiter indent x =
  tabulation
    delimiter
    ( [T.append indent (name x), T.pack (showN 0 (weight x))]
        <> (T.pack . showN 4 <$> ([coverage, share] <*> [x]))
    )

tabulation :: T.Text -> [T.Text] -> T.Text
tabulation delimiter =
  T.intercalate (T.append delimiter " ")
    . zipWith (`T.justifyLeft` ' ') [31, 9, 9, 9]

justifyRight :: Int -> a -> [a] -> [a]
justifyRight n c = (drop . length) <*> (replicate n c <>)

showN :: Int -> Float -> String
showN p n = justifyRight 7 ' ' (showFFloat (Just p) n "")
Output:
NAME_HIERARCHY                 | WEIGHT   | COVERAGE | SHARE OF RESIDUE
cleaning                       |       1  |  0.4092  |  0.5908  
    house1                     |      40  |  0.3312  |  0.2675  
        bedrooms               |       1  |  0.2500  |  0.0375  
        bathrooms              |       1  |  0.5000  |  0.0250  
            bathroom1          |       1  |  0.5000  |  0.0083  
            bathroom2          |       1  |  0.0000  |  0.0167  
            outside_lavatory   |       1  |  1.0000  |  0.0000  
        attic                  |       1  |  0.7500  |  0.0125  
        kitchen                |       1  |  0.1000  |  0.0450  
        living_rooms           |       1  |  0.2500  |  0.0375  
            lounge             |       1  |  0.0000  |  0.0125  
            dining_room        |       1  |  0.0000  |  0.0125  
            conservatory       |       1  |  0.0000  |  0.0125  
            playroom           |       1  |  1.0000  |  0.0000  
        basement               |       1  |  0.0000  |  0.0500  
        garage                 |       1  |  0.0000  |  0.0500  
        garden                 |       1  |  0.8000  |  0.0100  
    house2                     |      60  |  0.4611  |  0.3233  
        upstairs               |       1  |  0.1500  |  0.1700  
            bedrooms           |       1  |  0.0000  |  0.0500  
                suite_1        |       1  |  0.0000  |  0.0125  
                suite_2        |       1  |  0.0000  |  0.0125  
                bedroom_3      |       1  |  0.0000  |  0.0125  
                bedroom_4      |       1  |  0.0000  |  0.0125  
            bathroom           |       1  |  0.0000  |  0.0500  
            toilet             |       1  |  0.0000  |  0.0500  
            attics             |       1  |  0.6000  |  0.0200  
        groundfloor            |       1  |  0.3167  |  0.1367  
            kitchen            |       1  |  0.0000  |  0.0333  
            living_rooms       |       1  |  0.0000  |  0.0333  
                lounge         |       1  |  0.0000  |  0.0083  
                dining_room    |       1  |  0.0000  |  0.0083  
                conservatory   |       1  |  0.0000  |  0.0083  
                playroom       |       1  |  0.0000  |  0.0083  
            wet_room_&_toilet  |       1  |  0.0000  |  0.0333  
            garage             |       1  |  0.0000  |  0.0333  
            garden             |       1  |  0.9000  |  0.0033  
            hot_tub_suite      |       1  |  1.0000  |  0.0000  
        basement               |       1  |  0.9167  |  0.0167  
            cellars            |       1  |  1.0000  |  0.0000  
            wine_cellar        |       1  |  1.0000  |  0.0000  
            cinema             |       1  |  0.7500  |  0.0167  

J

Implementation part 1 of 4 (raw data):

raw=: 0 :0
NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |        |          |
    house1                      |40      |          |
        bedrooms                |        |0.25      |
        bathrooms               |        |          |
            bathroom1           |        |0.5       |
            bathroom2           |        |          |
            outside_lavatory    |        |1         |
        attic                   |        |0.75      |
        kitchen                 |        |0.1       |
        living_rooms            |        |          |
            lounge              |        |          |
            dining_room         |        |          |
            conservatory        |        |          |
            playroom            |        |1         |
        basement                |        |          |
        garage                  |        |          |
        garden                  |        |0.8       |
    house2                      |60      |          |
        upstairs                |        |          |
            bedrooms            |        |          |
                suite_1         |        |          |
                suite_2         |        |          |
                bedroom_3       |        |          |
                bedroom_4       |        |          |
            bathroom            |        |          |
            toilet              |        |          |
            attics              |        |0.6       |
        groundfloor             |        |          |
            kitchen             |        |          |
            living_rooms        |        |          |
                lounge          |        |          |
                dining_room     |        |          |
                conservatory    |        |          |
                playroom        |        |          |
            wet_room_&_toilet   |        |          |
            garage              |        |          |
            garden              |        |0.9       |
            hot_tub_suite       |        |1         |
        basement                |        |          |
            cellars             |        |1         |
            wine_cellar         |        |1         |
            cinema              |        |0.75      |
)

Implementation part 2 of 4 (unpacking raw data):

labels=: {.<;._2;._2 raw
'hier wspec cspec'=:|:}.<;._2;._2 raw
level=: (%+./) (0 i.~' '&=)"1 hier
weight=: (+ 0=]) ,".wspec
coverage=:  ,".cspec

To understand this implementation, it's best to run it and inspect the data.

That said:

each of the above names is a column variable (with one value for each row in the dataset).

level has values 0, 1, 2, 3 or 4 (depending on depth of indent), and the calculation relies on each indent level using the same number of spaces. It might be smarter to use level=: (i.~ ~.) (0 i.~' '&=)"1 hier which only relies on consistent indentation at each level, but ultimately a general case implementation would have to enforce an indentation standard and that sort of mechanism is out of scope for this task.

weight fills in the blanks for weights (1 if not otherwise specified). This calculation is simplified because we do not have to worry about any explicitly 0 weights.

coverage fills in the blanks for coverage (0 if not otherwise specified).

Implementation part 3 of 4 (translation of leaf coverage to functional coverage):

merge=: ;@(({.@[,(+}.)~)&.> [: +/\1,_1}.#@>)
unrooted=: ([: merge <@(_1,$:@}.);.1)^:(0<#)
parent=:  unrooted level
parent_cover=: (] (1}.~.parent)}~ 1}. * %&(parent +//. ]) [)^:_

unrooted translates indentation information to a parent tree structure. However, the limitations of recursion require we distinguish the parent node from its children, so we use _1 to denote the parent node of the recursive intermediate result unrooted trees. (This works well with using arithmetic to adjust sub-tree indices based on the lengths of preceding sub-trees.) merge combines a boxed sequence of these subtrees to form a single tree - we also rely on the first node of each tree being both _1 and the root node.

Thus, parent_cover propagates coverage to parent nodes based on the weighted average of coverage at the children.

Task example part 4 of 4 (format and show result):

   1 1 }._1 }.":labels,each ":each hier;(,.weight);,.weight parent_cover coverage
NAME_HIERARCHY                  WEIGHT  COVERAGE  
cleaning                         1      0.409167  
    house1                      40       0.33125  
        bedrooms                 1          0.25  
        bathrooms                1           0.5  
            bathroom1            1           0.5  
            bathroom2            1             0  
            outside_lavatory     1             1  
        attic                    1          0.75  
        kitchen                  1           0.1  
        living_rooms             1          0.25  
            lounge               1             0  
            dining_room          1             0  
            conservatory         1             0  
            playroom             1             1  
        basement                 1             0  
        garage                   1             0  
        garden                   1           0.8  
    house2                      60      0.461111  
        upstairs                 1          0.15  
            bedrooms             1             0  
                suite_1          1             0  
                suite_2          1             0  
                bedroom_3        1             0  
                bedroom_4        1             0  
            bathroom             1             0  
            toilet               1             0  
            attics               1           0.6  
        groundfloor              1      0.316667  
            kitchen              1             0  
            living_rooms         1             0  
                lounge           1             0  
                dining_room      1             0  
                conservatory     1             0  
                playroom         1             0  
            wet_room_&_toilet    1             0  
            garage               1             0  
            garden               1           0.9  
            hot_tub_suite        1             1  
        basement                 1      0.916667  
            cellars              1             1  
            wine_cellar          1             1  
            cinema               1          0.75  

Extra credit:

trace=: (~.@,each  (0 >. parent)&{)^:_  i.#parent
power=: */@:{&(parent (] % (i.~ ~.)@[ { +//.) weight)@> trace

   power*1-weight parent_cover coverage
0.590833 0.2675 0.0375 0.025 0.00833333 0.0166667 0 0.0125 0.045 0.0375 0.0125 0.0125 0.0125 0 0.05 0.05 0.01 0.323333 0.17 0.05 0.0125 0.0125 0.0125 0.0125 0.05 0.05 0.02 0.136667 0.0333333 0.0333333 0.00833333 0.00833333 0.00833333 0.00833333 0.0333333 0.0333333 0.00333333 0 0.0166667 0 0 0.0166667

Explanation:

trace is, for each node, the set of nodes (or indices of nodes - since we use indices to identify nodes) leading from that node to its root.

parent (] % (i.~ ~.)@[ { +//.) weight is the weight of each node divided by the total weight for all nodes with the same parent.

power is the product of these relative weights for each member of the trace.

And, weight parent_cover coverage was the functional coverage for each node.

Java

import java.util.ArrayList;
import java.util.List;

public final class FunctionalCoverageTree {

	public static void main(String[] aArgs) {		
		FCNode cleaning = new FCNode("Cleaning", 1, 0.0);
		
		List<FCNode> houses = List.of(
			new FCNode("House_1", 40, 0.0),
			new FCNode("House_2", 60, 0.0) );		
		cleaning.addChildren(houses);
		
		List<FCNode> house_1 = List.of( 
			new FCNode("Bedrooms", 1, 0.25), 
			new FCNode("Bathrooms", 1, 0.0), 
			new FCNode("Attic", 1, 0.75),
			new FCNode("Kitchen", 1, 0.1),
			new FCNode("Living_rooms", 1, 0.0),
			new FCNode("Basement", 1, 0.0),
			new FCNode("Garage", 1, 0.0),
			new FCNode("Garden",1, 0.8) );		
		houses.get(0).addChildren(house_1);
		
		List<FCNode> bathrooms_house_1 = List.of(
			new FCNode("Bathroom_1", 1, 0.5),
			new FCNode("Bathroom_2", 1, 0.0),
			new FCNode("Outside_lavatory", 1, 1.0) );
		house_1.get(1).addChildren(bathrooms_house_1);
		
		List<FCNode> living_rooms_house_1 = List.of( 
			new FCNode("lounge", 1, 0.0),
			new FCNode("Dining_room", 1, 0.0),
			new FCNode("Conservatory", 1, 0.0),
			new FCNode("Playroom", 1, 1.0) );
		house_1.get(4).addChildren(living_rooms_house_1);
		
		List<FCNode> house_2 = List.of(
			new FCNode("Upstairs", 1, 0.15),
			new FCNode("Ground_floor", 1, 0.316667),
			new FCNode("Basement", 1, 0.916667));
		houses.get(1).addChildren(house_2);
		
		List<FCNode> upstairs = List.of(
			new FCNode("Bedrooms", 1, 0.0),
			new FCNode("Bathroom", 1, 0.0),
			new FCNode("Toilet", 1, 0.0),
			new FCNode("Attics", 1, 0.6) );
		house_2.get(0).addChildren(upstairs);
		
		List<FCNode> ground_floor = List.of(
			new FCNode("Kitchen", 1, 0.0),
			new FCNode("Living_rooms", 1, 0.0), 
			new FCNode("Wet_room_&_toilet", 1, 0.0),
			new FCNode("Garage", 1, 0.0),
			new FCNode("Garden", 1, 0.9),
			new FCNode("Hot_tub_suite", 1, 1.0) );
		house_2.get(1).addChildren(ground_floor);
		
		List<FCNode> basement = List.of( 
			new FCNode("Cellars", 1, 1.0),
			new FCNode("Wine_cellar", 1, 1.0),
			new FCNode("Cinema", 1, 0.75) );
		house_2.get(2).addChildren(basement);
		
		List<FCNode> bedrooms = List.of(
			new FCNode("Suite_1", 1, 0.0),
			new FCNode("Suite_2", 1, 0.0),
			new FCNode("Bedroom_3",1, 0.0),
			new FCNode("Bedroom_4",1, 0.0) );
		upstairs.get(0).addChildren(bedrooms);
		
		List<FCNode> living_rooms_house_2 = List.of(
			new FCNode("lounge", 1, 0.0),
			new FCNode("Dining_room", 1, 0.0),
			new FCNode("Conservatory", 1, 0.0),
			new FCNode("Playroom", 1, 0.0) );
		ground_floor.get(1).addChildren(living_rooms_house_2);		
		
		final double overallCoverage = cleaning.getCoverage();
		System.out.println("OVERALL COVERAGE = " + String.format("%.6f", overallCoverage) + System.lineSeparator());
		System.out.println("NAME HIERARCHY                  | WEIGHT | COVERAGE |" );
		System.out.println("--------------------------------|--------|----------|");
		cleaning.display();
		System.out.println();
		
		basement.get(2).setCoverage(1.0); // Change House_2 Cinema node coverage to 1.0		
		final double updatedCoverage = cleaning.getCoverage();
	    final double difference = updatedCoverage - overallCoverage;
	    System.out.println("If the coverage of the House_2 Cinema node were increased from 0.75 to 1.0");
	    System.out.print("the overall coverage would increase by ");
	    System.out.println(String.format("%.6f%s%.6f", difference, " to ", updatedCoverage));;   
	    basement.get(2).setCoverage(0.75); // Restore to House_2 Cinema node coverage to its original value
	}

}

final class FCNode {
	
	public FCNode(String aName, int aWeight, double aCoverage) {
		name = aName;
		weight = aWeight;
		coverage = aCoverage;
	}
	
	public void addChildren(List<FCNode> aNodes) {
	    for ( FCNode node : aNodes ) {
	    	node.parent = this;
	    	children.add(node);
	    	updateCoverage();
	    }
	}	
	
	public double getCoverage() {
		return coverage;
	}
	
	public void setCoverage(double aCoverage) {
		if ( coverage != aCoverage ) {
		    coverage = aCoverage;
		    if ( parent != null ) {
		    	parent.updateCoverage();
		    }
		}		
	}
	
	public void display() {
		display(0);
	}
	
	private void updateCoverage() {
		double value1 = 0.0;
	    int value2 = 0;
	    for ( FCNode node : children ) {
	    	value1 += node.weight * node.coverage;
	    	value2 += node.weight;
	    }
	    
	    setCoverage(value1 / value2);
	}	
	
	private void display(int aLevel) {		
		final String initial = " ".repeat(4 * aLevel) + name;
		final String padding = " ".repeat(NAME_FIELD_WIDTH - initial.length()); 
		System.out.print(initial + padding + "|");
		System.out.print("  " + String.format("%3d", weight) + "   |");
		System.out.println(" " + String.format("%.6f", coverage) + " |");

		for ( FCNode child : children ) {
			child.display(aLevel + 1);
		}
	}	
	
	private String name;
	private int weight;
	private double coverage;
	private FCNode parent;
	private List<FCNode> children = new ArrayList<FCNode>();
	
	private static final int NAME_FIELD_WIDTH = 32;
	
}
Output:
OVERALL COVERAGE = 0.409167

NAME HIERARCHY                  | WEIGHT | COVERAGE |
--------------------------------|--------|----------|
Cleaning                        |    1   | 0.409167 |
    House_1                     |   40   | 0.331250 |
        Bedrooms                |    1   | 0.250000 |
        Bathrooms               |    1   | 0.500000 |
            Bathroom_1          |    1   | 0.500000 |
            Bathroom_2          |    1   | 0.000000 |
            Outside_lavatory    |    1   | 1.000000 |
        Attic                   |    1   | 0.750000 |
        Kitchen                 |    1   | 0.100000 |
        Living_rooms            |    1   | 0.250000 |
            lounge              |    1   | 0.000000 |
            Dining_room         |    1   | 0.000000 |
            Conservatory        |    1   | 0.000000 |
            Playroom            |    1   | 1.000000 |
        Basement                |    1   | 0.000000 |
        Garage                  |    1   | 0.000000 |
        Garden                  |    1   | 0.800000 |
    House_2                     |   60   | 0.461111 |
        Upstairs                |    1   | 0.150000 |
            Bedrooms            |    1   | 0.000000 |
                Suite_1         |    1   | 0.000000 |
                Suite_2         |    1   | 0.000000 |
                Bedroom_3       |    1   | 0.000000 |
                Bedroom_4       |    1   | 0.000000 |
            Bathroom            |    1   | 0.000000 |
            Toilet              |    1   | 0.000000 |
            Attics              |    1   | 0.600000 |
        Ground_floor            |    1   | 0.316667 |
            Kitchen             |    1   | 0.000000 |
            Living_rooms        |    1   | 0.000000 |
                lounge          |    1   | 0.000000 |
                Dining_room     |    1   | 0.000000 |
                Conservatory    |    1   | 0.000000 |
                Playroom        |    1   | 0.000000 |
            Wet_room_&_toilet   |    1   | 0.000000 |
            Garage              |    1   | 0.000000 |
            Garden              |    1   | 0.900000 |
            Hot_tub_suite       |    1   | 1.000000 |
        Basement                |    1   | 0.916667 |
            Cellars             |    1   | 1.000000 |
            Wine_cellar         |    1   | 1.000000 |
            Cinema              |    1   | 0.750000 |

If the coverage of the House_2 Cinema node were increased from 0.75 to 1.0
the overall coverage would increase by 0.016667 to 0.425833

JavaScript

Parsing the outline text to a tree structure, and traversing this with two computations (one bottom-up, and one top-down), before serialising the updated tree to a new outline text.

Translation of: Haskell
Translation of: Python
(() => {
    'use strict';

    // updatedCoverageOutline :: String -> String
    const updatedCoverageOutline = outlineText => {
        const
            delimiter = '|',
            indentedLines = indentLevelsFromLines(lines(outlineText)),
            columns = init(tokenizeWith(delimiter)(snd(indentedLines[0])));

        // SERIALISATION OF UPDATED PARSE TREE (TO NEW OUTLINE TEXT)
        return tabulation(delimiter)(
            columns.concat('SHARE OF RESIDUE\n')
        ) + unlines(
            indentedLinesFromTree(
                showCoverage(delimiter))('    ')(

                // TWO TRAVERSAL COMPUTATIONS

                withResidueShares(1.0)(
                    foldTree(weightedCoverage)(

                        // PARSE TREE (FROM OUTLINE TEXT)
                        fmapTree(compose(
                            partialRecord, tokenizeWith(delimiter)
                        ))(fst(
                            forestFromLineIndents(tail(indentedLines))
                        ))
                    )
                ))
        );
    };

    // TEST -----------------------------------------------
    // main :: IO ()
    const main = () =>
        console.log(
            // strOutline is included as literal text
            // at the foot of this code listing.
            updatedCoverageOutline(strOutline)
        );

    // COVERAGE AND SHARES OF RESIDUE ---------------------

    // weightedCoverage :: Dict -> Forest Dict -> Tree Dict
    const weightedCoverage = x => xs => {
        const
            cws = map(compose(
                fanArrow(x => x.coverage)(x => x.weight),
                root
            ))(xs),
            totalWeight = cws.reduce((a, tpl) => a + snd(tpl), 0);
        return Node(
            insertDict('coverage')(
                cws.reduce((a, tpl) => {
                    const [c, w] = Array.from(tpl);
                    return a + (c * w);
                }, x.coverage) / (
                    0 < totalWeight ? totalWeight : 1
                )
            )(x)
        )(xs);
    };


    // withResidueShares :: Float -> Tree Dict -> Tree Dict
    const withResidueShares = shareOfTotal => tree => {
        const go = fraction => node => {
            const
                nodeRoot = node.root,
                forest = node.nest,
                weights = forest.map(x => x.root.weight),
                weightTotal = sum(weights);
            return Node(
                insertDict('share')(
                    fraction * (1 - nodeRoot.coverage)
                )(nodeRoot)
            )(
                zipWith(go)(
                    weights.map(w => fraction * (w / weightTotal))
                )(forest)
            );
        };
        return go(shareOfTotal)(tree);
    };


    // OUTLINE PARSED TO TREE -----------------------------

    // forestFromLineIndents :: [(Int, String)] -> [Tree String]
    const forestFromLineIndents = tuples => {
        const go = xs =>
            0 < xs.length ? (() => {
                const [n, s] = Array.from(xs[0]);
                // Lines indented under this line,
                // tupled with all the rest.
                const [firstTreeLines, rest] = Array.from(
                    span(x => n < x[0])(xs.slice(1))
                );
                // This first tree, and then the rest.
                return [Node(s)(go(firstTreeLines))]
                    .concat(go(rest));
            })() : [];
        return go(tuples);
    };

    // indentLevelsFromLines :: [String] -> [(Int, String)]
    const indentLevelsFromLines = xs => {
        const
            indentTextPairs = xs.map(compose(
                firstArrow(length), span(isSpace)
            )),
            indentUnit = minimum(indentTextPairs.flatMap(pair => {
                const w = fst(pair);
                return 0 < w ? [w] : [];
            }));
        return indentTextPairs.map(
            firstArrow(flip(div)(indentUnit))
        );
    };

    // partialRecord :: [String] -> Dict
    const partialRecord = xs => {
        const [name, weightText, coverageText] = take(3)(
            xs.concat(['', '', ''])
        );
        return {
            name: name || '?',
            weight: parseFloat(weightText) || 1.0,
            coverage: parseFloat(coverageText) || 0.0,
            share: 0.0
        };
    };

    // tokenizeWith :: String -> String -> [String]
    const tokenizeWith = delimiter =>
        // A sequence of trimmed tokens obtained by
        // splitting s on the supplied delimiter.
        s => s.split(delimiter).map(x => x.trim());


    // TREE SERIALIZED TO OUTLINE -------------------------

    // indentedLinesFromTree :: (String -> a -> String) ->
    // String -> Tree a -> [String]
    const indentedLinesFromTree = showRoot =>
        strTab => tree => {
            const go = indent =>
                node => [showRoot(indent)(node.root)]
                .concat(node.nest.flatMap(go(strTab + indent)));
            return go('')(tree);
        };

    // showN :: Int -> Float -> String
    const showN = p =>
        n => justifyRight(7)(' ')(n.toFixed(p));

    // showCoverage :: String -> String -> Dict -> String
    const showCoverage = delimiter =>
        indent => x => tabulation(delimiter)(
            [indent + x.name, showN(0)(x.weight)]
            .concat([x.coverage, x.share].map(showN(4)))
        );

    // tabulation :: String -> [String] -> String
    const tabulation = delimiter =>
        // Up to 4 tokens drawn from the argument list,
        // as a single string with fixed left-justified
        // white-space widths, between delimiters.
        compose(
            intercalate(delimiter + ' '),
            zipWith(flip(justifyLeft)(' '))([31, 9, 9, 9])
        );


    // GENERIC AND REUSABLE FUNCTIONS ---------------------

    // Node :: a -> [Tree a] -> Tree a
    const Node = v => xs => ({
        type: 'Node',
        root: v, // any type of value (consistent across tree)
        nest: xs || []
    });

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a => b => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        x => fs.reduceRight((a, f) => f(a), x);

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];

    // div :: Int -> Int -> Int
    const div = x => y => Math.floor(x / y);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl => fr => e =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // Compose a function from a simple value to a tuple of
    // the separate outputs of two different functions

    // fanArrow (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c))
    const fanArrow = f => g => x => Tuple(f(x))(
        g(x)
    );

    // Lift a simple function to one which applies to a tuple,
    // transforming only the first item of the tuple

    // firstArrow :: (a -> b) -> ((a, c) -> (b, c))
    const firstArrow = f => xy => Tuple(f(xy[0]))(
        xy[1]
    );

    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = f =>
        1 < f.length ? (
            (a, b) => f(b, a)
        ) : (x => y => f(y)(x));

    // fmapTree :: (a -> b) -> Tree a -> Tree b
    const fmapTree = f => tree => {
        const go = node => Node(f(node.root))(
            node.nest.map(go)
        );
        return go(tree);
    };

    // foldTree :: (a -> [b] -> b) -> Tree a -> b
    const foldTree = f => tree => {
        const go = node => f(node.root)(
            node.nest.map(go)
        );
        return go(tree);
    };

    // foldl1 :: (a -> a -> a) -> [a] -> a
    const foldl1 = f => xs =>
        1 < xs.length ? xs.slice(1)
        .reduce(uncurry(f), xs[0]) : xs[0];

    // fst :: (a, b) -> a
    const fst = tpl => tpl[0];

    // init :: [a] -> [a]
    const init = xs =>
        0 < xs.length ? (
            xs.slice(0, -1)
        ) : undefined;

    // insertDict :: String -> a -> Dict -> Dict
    const insertDict = k => v => dct =>
        Object.assign({}, dct, {
            [k]: v
        });

    // intercalate :: [a] -> [[a]] -> [a]
    // intercalate :: String -> [String] -> String
    const intercalate = sep =>
        xs => xs.join(sep);

    // isSpace :: Char -> Bool
    const isSpace = c => /\s/.test(c);

    // justifyLeft :: Int -> Char -> String -> String
    const justifyLeft = n => cFiller => s =>
        n > s.length ? (
            s.padEnd(n, cFiller)
        ) : s;

    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = n => cFiller => s =>
        n > s.length ? (
            s.padStart(n, cFiller)
        ) : s;

    // length :: [a] -> Int
    const length = xs =>
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;

    // lines :: String -> [String]
    const lines = s => s.split(/[\r\n]/);

    // map :: (a -> b) -> [a] -> [b]
    const map = f => xs =>
        (Array.isArray(xs) ? (
            xs
        ) : xs.split('')).map(f);

    // minimum :: Ord a => [a] -> a
    const minimum = xs =>
        0 < xs.length ? (
            foldl1(a => x => x < a ? x : a)(xs)
        ) : undefined;

    // root :: Tree a -> a
    const root = tree => tree.root;

    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // span :: (a -> Bool) -> [a] -> ([a], [a])
    const span = p => xs => {
        const iLast = xs.length - 1;
        return splitAt(
            until(i => iLast < i || !p(xs[i]))(
                succ
            )(0)
        )(xs);
    };

    // splitAt :: Int -> [a] -> ([a], [a])
    const splitAt = n => xs =>
        Tuple(xs.slice(0, n))(
            xs.slice(n)
        );

    // succ :: Enum a => a -> a
    const succ = x =>
        1 + x;

    // sum :: [Num] -> Num
    const sum = xs =>
        xs.reduce((a, x) => a + x, 0);

    // tail :: [a] -> [a]
    const tail = xs =>
        0 < xs.length ? xs.slice(1) : [];

    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = n => xs =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));

    // uncurry :: (a -> b -> c) -> ((a, b) -> c)
    const uncurry = f =>
        (x, y) => f(x)(y);

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = p => f => x => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f => xs => ys =>
        xs.slice(
            0, Math.min(xs.length, ys.length)
        ).map((x, i) => f(x)(ys[i]));

    // SOURCE OUTLINE -----------------------------------------

    const strOutline = `NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |        |          |
    house1                      |40      |          |
        bedrooms                |        |0.25      |
        bathrooms               |        |          |
            bathroom1           |        |0.5       |
            bathroom2           |        |          |
            outside_lavatory    |        |1         |
        attic                   |        |0.75      |
        kitchen                 |        |0.1       |
        living_rooms            |        |          |
            lounge              |        |          |
            dining_room         |        |          |
            conservatory        |        |          |
            playroom            |        |1         |
        basement                |        |          |
        garage                  |        |          |
        garden                  |        |0.8       |
    house2                      |60      |          |
        upstairs                |        |          |
            bedrooms            |        |          |
                suite_1         |        |          |
                suite_2         |        |          |
                bedroom_3       |        |          |
                bedroom_4       |        |          |
            bathroom            |        |          |
            toilet              |        |          |
            attics              |        |0.6       |
        groundfloor             |        |          |
            kitchen             |        |          |
            living_rooms        |        |          |
                lounge          |        |          |
                dining_room     |        |          |
                conservatory    |        |          |
                playroom        |        |          |
            wet_room_&_toilet   |        |          |
            garage              |        |          |
            garden              |        |0.9       |
            hot_tub_suite       |        |1         |
        basement                |        |          |
            cellars             |        |1         |
            wine_cellar         |        |1         |
            cinema              |        |0.75      |`;

    // MAIN ---
    return main();
})();
Output:
NAME_HIERARCHY                 | WEIGHT   | COVERAGE | SHARE OF RESIDUE
cleaning                       |       1  |  0.4092  |  0.5908  
    house1                     |      40  |  0.3313  |  0.2675  
        bedrooms               |       1  |  0.2500  |  0.0375  
        bathrooms              |       1  |  0.5000  |  0.0250  
            bathroom1          |       1  |  0.5000  |  0.0083  
            bathroom2          |       1  |  0.0000  |  0.0167  
            outside_lavatory   |       1  |  1.0000  |  0.0000  
        attic                  |       1  |  0.7500  |  0.0125  
        kitchen                |       1  |  0.1000  |  0.0450  
        living_rooms           |       1  |  0.2500  |  0.0375  
            lounge             |       1  |  0.0000  |  0.0125  
            dining_room        |       1  |  0.0000  |  0.0125  
            conservatory       |       1  |  0.0000  |  0.0125  
            playroom           |       1  |  1.0000  |  0.0000  
        basement               |       1  |  0.0000  |  0.0500  
        garage                 |       1  |  0.0000  |  0.0500  
        garden                 |       1  |  0.8000  |  0.0100  
    house2                     |      60  |  0.4611  |  0.3233  
        upstairs               |       1  |  0.1500  |  0.1700  
            bedrooms           |       1  |  0.0000  |  0.0500  
                suite_1        |       1  |  0.0000  |  0.0125  
                suite_2        |       1  |  0.0000  |  0.0125  
                bedroom_3      |       1  |  0.0000  |  0.0125  
                bedroom_4      |       1  |  0.0000  |  0.0125  
            bathroom           |       1  |  0.0000  |  0.0500  
            toilet             |       1  |  0.0000  |  0.0500  
            attics             |       1  |  0.6000  |  0.0200  
        groundfloor            |       1  |  0.3167  |  0.1367  
            kitchen            |       1  |  0.0000  |  0.0333  
            living_rooms       |       1  |  0.0000  |  0.0333  
                lounge         |       1  |  0.0000  |  0.0083  
                dining_room    |       1  |  0.0000  |  0.0083  
                conservatory   |       1  |  0.0000  |  0.0083  
                playroom       |       1  |  0.0000  |  0.0083  
            wet_room_&_toilet  |       1  |  0.0000  |  0.0333  
            garage             |       1  |  0.0000  |  0.0333  
            garden             |       1  |  0.9000  |  0.0033  
            hot_tub_suite      |       1  |  1.0000  |  0.0000  
        basement               |       1  |  0.9167  |  0.0167  
            cellars            |       1  |  1.0000  |  0.0000  
            wine_cellar        |       1  |  1.0000  |  0.0000  
            cinema             |       1  |  0.7500  |  0.0167 

Julia

Most implementations of functional coverage are going to store values in a database. The implementation stores the tree in a CSV file with an index to the parent of each entry to allow reconstitution of the tree.

using CSV, DataFrames, Formatting

function updatecoverage(dfname, outputname)
    df = CSV.read(dfname)
    dchild = Dict{Int, Vector{Int}}([i => Int[] for i in 0:maximum(df[!, 1])])

    for row in eachrow(df)
        push!(dchild[row[3]], row[1])
    end

    function coverage(t)
        return dchild[t] == [] ? df[t, :COVERAGE] * df[t, :WEIGHT] :
            sum(coverage, dchild[t]) / sum(x -> df[x, :WEIGHT], dchild[t]) * df[t, :WEIGHT]
    end

    df[!, :COVERAGE] .= coverage.(df.NUMBER)

    function possibleincrease(t)
        if !isempty(dchild[t])
            return 0.0
        else
            newcoverage = deepcopy(df.COVERAGE)
            newcoverage[t] = 1.0
            oldcoverage = newcoverage[1]
            function potentialcoverage(t)
                return dchild[t] == [] ? newcoverage[t] * df[t, :WEIGHT] :
                    sum(potentialcoverage, dchild[t]) / sum(x -> df[x, :WEIGHT],
                        dchild[t]) * df[t, :WEIGHT]
            end

            newcoverage .= potentialcoverage.(df[!, 1])
            return newcoverage[1] - oldcoverage
        end
    end

    df.POTENTIAL = possibleincrease.(df[!, 1])

    CSV.write(outputname, df)
end

function displaycoveragedb(dfname)
    df = CSV.read(dfname)
    indentlevel(t) = (i = 0; while (j = df[t, 3]) != 0 i += 1; t = j end; i)
    indent1 = [indentlevel(i) for i in df.NUMBER]
    maxindent = maximum(indent1)
    indent2 = maxindent .- indent1
    showpot = size(df)[2] == 6
    println("INDEX       NAME_HIERARCHY                         WEIGHT      COVERAGE    (POTENTIAL INCREASE)")
    for (i, row) in enumerate(eachrow(df))
        println(rpad(row[1], 7), "       "^indent1[i], rpad(row[2], 20), "       "^indent2[i],
            rpad(row[4], 8), rpad(format(row[5]), 12), showpot && row[6] != 0 ? format(row[6]) : "")
    end
end

const dbname = "coverage.csv"
const newdbname = "coverageupdated.csv"

println("Input data:")
displaycoveragedb(dbname)
updatecoverage(dbname, newdbname)
println("\nUpdated data:")
displaycoveragedb(newdbname)
Output:
Input data:
INDEX       NAME_HIERARCHY                         WEIGHT      COVERAGE    (POTENTIAL INCREASE)
1      cleaning                                        1       0
2             house1                                   40      0
3                    bedrooms                          1       0.25
4                    bathrooms                         1       0
5                           bathroom1                  1       0.5
6                           bathroom2                  1       0
7                           outside_lavatory           1       1
8                    attic                             1       0.75
9                    kitchen                           1       0.1
10                   living_rooms                      1       0
11                          lounge                     1       0
12                          dining_room                1       0
13                          conservatory               1       0
14                          playroom                   1       1
15                   basement                          1       0
16                   garage                            1       0
17                   garden                            1       0.8
18            house2                                   60      0
19                   upstairs                          1       0
20                          bedrooms                   1       0
21                                 suite_1             1       0
22                                 suite_2             1       0
23                                 bedroom_3           1       0
24                                 bedroom_4           1       0
25                          bathroom                   1       0
26                          toilet                     1       0
27                          attics                     1       0.6
28                   groundfloor                       1       0
29                          kitchen                    1       0
30                          living_rooms               1       0
31                                 lounge              1       0
32                                 dining_room         1       0
33                                 conservatory        1       0
34                                 playroom            1       0
35                          wet_room_&_toilet          1       0
36                          garage                     1       0
37                          garden                     1       0.9
38                          hot_tub_suite              1       1
39                   basement                          1       0
40                          cellars                    1       1
41                          wine_cellar                1       1
42                          cinema                     1       0.75

Updated data:
INDEX       NAME_HIERARCHY                         WEIGHT      COVERAGE    (POTENTIAL INCREASE)
1      cleaning                                        1       0.409167
2             house1                                   40      13.25
3                    bedrooms                          1       0.25        0.0375
4                    bathrooms                         1       0.5
5                           bathroom1                  1       0.5         0.008333
6                           bathroom2                  1       0           0.016667
7                           outside_lavatory           1       1
8                    attic                             1       0.75        0.0125
9                    kitchen                           1       0.1         0.045
10                   living_rooms                      1       0.25
11                          lounge                     1       0           0.0125
12                          dining_room                1       0           0.0125
13                          conservatory               1       0           0.0125
14                          playroom                   1       1
15                   basement                          1       0           0.05
16                   garage                            1       0           0.05
17                   garden                            1       0.8         0.01
18            house2                                   60      27.666667
19                   upstairs                          1       0.15
20                          bedrooms                   1       0
21                                 suite_1             1       0           0.0125
22                                 suite_2             1       0           0.0125
23                                 bedroom_3           1       0           0.0125
24                                 bedroom_4           1       0           0.0125
25                          bathroom                   1       0           0.05
26                          toilet                     1       0           0.05
27                          attics                     1       0.6         0.02
28                   groundfloor                       1       0.316667
29                          kitchen                    1       0           0.033333
30                          living_rooms               1       0
31                                 lounge              1       0           0.008333
32                                 dining_room         1       0           0.008333
33                                 conservatory        1       0           0.008333
34                                 playroom            1       0           0.008333
35                          wet_room_&_toilet          1       0           0.033333
36                          garage                     1       0           0.033333
37                          garden                     1       0.9         0.003333
38                          hot_tub_suite              1       1
39                   basement                          1       0.916667
40                          cellars                    1       1
41                          wine_cellar                1       1
42                          cinema                     1       0.75        0.016667

The input CSV file used:

NUMBER,NAME_HIERARCHY,PARENT_NUMBER,WEIGHT,COVERAGE
1,cleaning,0,1,0
2,house1,1,40,0
3,bedrooms,2,1,0.25
4,bathrooms,2,1,0
5,bathroom1,4,1,0.5
6,bathroom2,4,1,0
7,outside_lavatory,4,1,1
8,attic,2,1,0.75
9,kitchen,2,1,0.1
10,living_rooms,2,1,0
11,lounge,10,1,0
12,dining_room,10,1,0
13,conservatory,10,1,0
14,playroom,10,1,1
15,basement,2,1,0
16,garage,2,1,0
17,garden,2,1,0.8
18,house2,1,60,0
19,upstairs,18,1,0
20,bedrooms,19,1,0
21,suite_1,20,1,0
22,suite_2,20,1,0
23,bedroom_3,20,1,0
24,bedroom_4,20,1,0
25,bathroom,19,1,0
26,toilet,19,1,0
27,attics,19,1,0.6
28,groundfloor,18,1,0
29,kitchen,28,1,0
30,living_rooms,28,1,0
31,lounge,30,1,0
32,dining_room,30,1,0
33,conservatory,30,1,0
34,playroom,30,1,0
35,wet_room_&_toilet,28,1,0
36,garage,28,1,0
37,garden,28,1,0.9
38,hot_tub_suite,28,1,1
39,basement,18,1,0
40,cellars,39,1,1
41,wine_cellar,39,1,1
42,cinema,39,1,0.75

Kotlin

// version 1.2.10

class FCNode(val name: String, val weight: Int = 1, coverage: Double = 0.0) {

    var coverage = coverage
        set(value) {
            if (field != value) {
               field = value
               // update any parent's coverage
               if (parent != null) parent!!.updateCoverage()
            }
        }

    val children = mutableListOf<FCNode>()
    var parent: FCNode? = null

    fun addChildren(nodes: List<FCNode>) {
        children.addAll(nodes)
        nodes.forEach { it.parent = this }
        updateCoverage()
    }

    private fun updateCoverage() {
        val v1 = children.sumByDouble { it.weight * it.coverage }
        val v2 = children.sumBy { it.weight }
        coverage = v1 / v2
    }

    fun show(level: Int = 0) {
        val indent = level * 4
        val nl = name.length + indent
        print(name.padStart(nl))
        print("|".padStart(32 - nl))
        print("  %3d   |".format(weight))
        println(" %8.6f |".format(coverage))
        if (children.size == 0) return
        for (child in children) child.show(level + 1)
    }
}

val houses = listOf(
    FCNode("house1", 40),
    FCNode("house2", 60)
)

val house1 = listOf(
    FCNode("bedrooms", 1, 0.25),
    FCNode("bathrooms"),
    FCNode("attic", 1, 0.75),
    FCNode("kitchen", 1, 0.1),
    FCNode("living_rooms"),
    FCNode("basement"),
    FCNode("garage"),
    FCNode("garden", 1, 0.8)
)

val house2 = listOf(
    FCNode("upstairs"),
    FCNode("groundfloor"),
    FCNode("basement")
)

val h1Bathrooms = listOf(
    FCNode("bathroom1", 1, 0.5),
    FCNode("bathroom2"),
    FCNode("outside_lavatory", 1, 1.0)
)

val h1LivingRooms = listOf(
    FCNode("lounge"),
    FCNode("dining_room"),
    FCNode("conservatory"),
    FCNode("playroom", 1, 1.0)
)

val h2Upstairs = listOf(
    FCNode("bedrooms"),
    FCNode("bathroom"),
    FCNode("toilet"),
    FCNode("attics", 1, 0.6)
)

val h2Groundfloor = listOf(
    FCNode("kitchen"),
    FCNode("living_rooms"),
    FCNode("wet_room_&_toilet"),
    FCNode("garage"),
    FCNode("garden", 1, 0.9),
    FCNode("hot_tub_suite", 1, 1.0)
)

val h2Basement = listOf(
    FCNode("cellars", 1, 1.0),
    FCNode("wine_cellar", 1, 1.0),
    FCNode("cinema", 1, 0.75)
)

val h2UpstairsBedrooms = listOf(
    FCNode("suite_1"),
    FCNode("suite_2"),
    FCNode("bedroom_3"),
    FCNode("bedroom_4")
)

val h2GroundfloorLivingRooms = listOf(
    FCNode("lounge"),
    FCNode("dining_room"),
    FCNode("conservatory"),
    FCNode("playroom")
)

fun main(args: Array<String>) {
    val cleaning = FCNode("cleaning")

    house1[1].addChildren(h1Bathrooms)
    house1[4].addChildren(h1LivingRooms)
    houses[0].addChildren(house1)

    h2Upstairs[0].addChildren(h2UpstairsBedrooms)
    house2[0].addChildren(h2Upstairs)
    h2Groundfloor[1].addChildren(h2GroundfloorLivingRooms)
    house2[1].addChildren(h2Groundfloor)
    house2[2].addChildren(h2Basement)
    houses[1].addChildren(house2)

    cleaning.addChildren(houses)
    val topCoverage = cleaning.coverage
    println("TOP COVERAGE = ${"%8.6f".format(topCoverage)}\n")
    println("NAME HIERARCHY                 | WEIGHT | COVERAGE |")
    cleaning.show()

    h2Basement[2].coverage = 1.0  // change Cinema node coverage to 1.0
    val diff = cleaning.coverage - topCoverage
    println("\nIf the coverage of the Cinema node were increased from 0.75 to 1.0")
    print("the top level coverage would increase by ")
    println("${"%8.6f".format(diff)} to ${"%8.6f".format(topCoverage + diff)}")    
    h2Basement[2].coverage = 0.75  // restore to original value if required
}
Output:
TOP COVERAGE = 0.409167

NAME HIERARCHY                 | WEIGHT | COVERAGE |
cleaning                       |    1   | 0.409167 |
    house1                     |   40   | 0.331250 |
        bedrooms               |    1   | 0.250000 |
        bathrooms              |    1   | 0.500000 |
            bathroom1          |    1   | 0.500000 |
            bathroom2          |    1   | 0.000000 |
            outside_lavatory   |    1   | 1.000000 |
        attic                  |    1   | 0.750000 |
        kitchen                |    1   | 0.100000 |
        living_rooms           |    1   | 0.250000 |
            lounge             |    1   | 0.000000 |
            dining_room        |    1   | 0.000000 |
            conservatory       |    1   | 0.000000 |
            playroom           |    1   | 1.000000 |
        basement               |    1   | 0.000000 |
        garage                 |    1   | 0.000000 |
        garden                 |    1   | 0.800000 |
    house2                     |   60   | 0.461111 |
        upstairs               |    1   | 0.150000 |
            bedrooms           |    1   | 0.000000 |
                suite_1        |    1   | 0.000000 |
                suite_2        |    1   | 0.000000 |
                bedroom_3      |    1   | 0.000000 |
                bedroom_4      |    1   | 0.000000 |
            bathroom           |    1   | 0.000000 |
            toilet             |    1   | 0.000000 |
            attics             |    1   | 0.600000 |
        groundfloor            |    1   | 0.316667 |
            kitchen            |    1   | 0.000000 |
            living_rooms       |    1   | 0.000000 |
                lounge         |    1   | 0.000000 |
                dining_room    |    1   | 0.000000 |
                conservatory   |    1   | 0.000000 |
                playroom       |    1   | 0.000000 |
            wet_room_&_toilet  |    1   | 0.000000 |
            garage             |    1   | 0.000000 |
            garden             |    1   | 0.900000 |
            hot_tub_suite      |    1   | 1.000000 |
        basement               |    1   | 0.916667 |
            cellars            |    1   | 1.000000 |
            wine_cellar        |    1   | 1.000000 |
            cinema             |    1   | 0.750000 |

If the coverage of the Cinema node were increased from 0.75 to 1.0
the top level coverage would increase by 0.016667 to 0.425833

Lua

-- DATA:
local function node(name, weight, coverage, children)
  return { name=name, weight=weight or 1.0, coverage=coverage or 0.0, sumofweights=0, delta=0, children=children }
end

local root =
node("cleaning", nil, nil, {
  node("house1", 40, nil, {
    node("bedrooms", nil, 0.25),
    node("bathrooms", nil, nil, {
      node("bathroom1", nil, 0.5),
      node("bathroom2"),
      node("outside_lavatory", nil, 1)
    }),
    node("attic", nil, 0.75),
    node("kitchen", nil, 0.1),
    node("living_rooms", nil, nil, {
      node("lounge"),
      node("dining_room"),
      node("conservatory"),
      node("playroom",nil,1)
    }),
    node("basement"),
    node("garage"),
    node("garden", nil, 0.8)
  }),
  node("house2", 60, nil, {
    node("upstairs", nil, nil, {
      node("bedrooms", nil, nil, {
        node("suite_1"),
        node("suite_2"),
        node("bedroom_3"),
        node("bedroom_4")
      }),
      node("bathroom"),
      node("toilet"),
      node("attics", nil, 0.6)
    }),
    node("groundfloor", nil, nil, {
      node("kitchen"),
      node("living_rooms", nil, nil, {
        node("lounge"),
        node("dining_room"),
        node("conservatory"),
        node("playroom")
      }),
      node("wet_room_&_toilet"),
      node("garage"),
      node("garden", nil, 0.9),
      node("hot_tub_suite", nil, 1)
    }),
    node("basement", nil, nil, {
      node("cellars", nil, 1),
      node("wine_cellar", nil, 1),
      node("cinema", nil, 0.75)
    })
  })
})

-- TASK:
local function calccover(node)
  if (node.children) then
    local cnt, sum = 0, 0
    for _,child in ipairs(node.children) do
      local ccnt, csum = calccover(child)
      cnt, sum = cnt+ccnt, sum+csum
    end
    node.coverage = sum/cnt
    node.sumofweights = cnt -- just as prep for extra credit
  end
  return node.weight, node.coverage * node.weight
end
calccover(root)

-- EXTRA CREDIT:
local function calcdelta(node, power)
  node.delta = (1.0 - node.coverage) * power
  if (node.children) then
    for _,child in ipairs(node.children) do
      calcdelta(child, power * child.weight / node.sumofweights)
    end
  end
end
calcdelta(root,1)

-- OUTPUT:
local function printnode(node, space)
  print(string.format("%-32s|  %3.f   | %8.6f | %8.6f |", string.rep(" ",space)..node.name, node.weight, node.coverage, node.delta))
  if node.children then
    for _,child in ipairs(node.children) do printnode(child,space+4) end
  end
end
print("NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |DELTA     |")
printnode(root,0)
Output:
NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |DELTA     |
cleaning                        |    1   | 0.409167 | 0.590833 |
    house1                      |   40   | 0.331250 | 0.267500 |
        bedrooms                |    1   | 0.250000 | 0.037500 |
        bathrooms               |    1   | 0.500000 | 0.025000 |
            bathroom1           |    1   | 0.500000 | 0.008333 |
            bathroom2           |    1   | 0.000000 | 0.016667 |
            outside_lavatory    |    1   | 1.000000 | 0.000000 |
        attic                   |    1   | 0.750000 | 0.012500 |
        kitchen                 |    1   | 0.100000 | 0.045000 |
        living_rooms            |    1   | 0.250000 | 0.037500 |
            lounge              |    1   | 0.000000 | 0.012500 |
            dining_room         |    1   | 0.000000 | 0.012500 |
            conservatory        |    1   | 0.000000 | 0.012500 |
            playroom            |    1   | 1.000000 | 0.000000 |
        basement                |    1   | 0.000000 | 0.050000 |
        garage                  |    1   | 0.000000 | 0.050000 |
        garden                  |    1   | 0.800000 | 0.010000 |
    house2                      |   60   | 0.461111 | 0.323333 |
        upstairs                |    1   | 0.150000 | 0.170000 |
            bedrooms            |    1   | 0.000000 | 0.050000 |
                suite_1         |    1   | 0.000000 | 0.012500 |
                suite_2         |    1   | 0.000000 | 0.012500 |
                bedroom_3       |    1   | 0.000000 | 0.012500 |
                bedroom_4       |    1   | 0.000000 | 0.012500 |
            bathroom            |    1   | 0.000000 | 0.050000 |
            toilet              |    1   | 0.000000 | 0.050000 |
            attics              |    1   | 0.600000 | 0.020000 |
        groundfloor             |    1   | 0.316667 | 0.136667 |
            kitchen             |    1   | 0.000000 | 0.033333 |
            living_rooms        |    1   | 0.000000 | 0.033333 |
                lounge          |    1   | 0.000000 | 0.008333 |
                dining_room     |    1   | 0.000000 | 0.008333 |
                conservatory    |    1   | 0.000000 | 0.008333 |
                playroom        |    1   | 0.000000 | 0.008333 |
            wet_room_&_toilet   |    1   | 0.000000 | 0.033333 |
            garage              |    1   | 0.000000 | 0.033333 |
            garden              |    1   | 0.900000 | 0.003333 |
            hot_tub_suite       |    1   | 1.000000 | 0.000000 |
        basement                |    1   | 0.916667 | 0.016667 |
            cellars             |    1   | 1.000000 | 0.000000 |
            wine_cellar         |    1   | 1.000000 | 0.000000 |
            cinema              |    1   | 0.750000 | 0.016667 |

Nim

Translation of: Go
import strformat, strutils

type

  FCNode = ref object
    name: string
    weight: int
    coverage: float
    children: seq[FCNode]
    parent: FCNode

func newFCNode(name: string; weight: int; coverage: float): FCNode =
  FCNode(name: name, weight: weight, coverage: coverage)

# Forward reference.
func updateCoverage(n: FCNode)

func addChildren(n: FCNode; nodes: openArray[FCNode]) =
  for node in nodes:
    node.parent = n
  n.children = @nodes
  n.updateCoverage()

func setCoverage(n: FCNode; value: float) =
  if n.coverage != value:
    n.coverage = value
    # Update any parent's coverage.
    if not n.parent.isNil:
      n.parent.updateCoverage()

func updateCoverage(n: FCNode) =
  var v1 = 0.0
  var v2 = 0
  for node in n.children:
    v1 += node.weight.toFloat * node.coverage
    v2 += node.weight
  n.setCoverage(v1 / v2.toFloat)

proc show(n: FCNode; level: int) =
  let indent = level * 4
  let nl = n.name.len + indent
  const Sep = "|"
  echo &"{n.name.align(nl)}{Sep.align(32-nl)}  {n.weight:>3d}   | {n.coverage:8.6f} |"
  for child in n.children:
    child.show(level + 1)

#———————————————————————————————————————————————————————————————————————————————————————————————————

let houses = [newFCNode("house1", 40, 0), newFCNode("house2", 60, 0)]

let house1 = [
    newFCNode("bedrooms", 1, 0.25),
    newFCNode("bathrooms", 1, 0),
    newFCNode("attic", 1, 0.75),
    newFCNode("kitchen", 1, 0.1),
    newFCNode("living_rooms", 1, 0),
    newFCNode("basement", 1, 0),
    newFCNode("garage", 1, 0),
    newFCNode("garden", 1, 0.8)]

let house2 = [
    newFCNode("upstairs", 1, 0),
    newFCNode("groundfloor", 1, 0),
    newFCNode("basement", 1, 0)]

let h1Bathrooms = [
    newFCNode("bathroom1", 1, 0.5),
    newFCNode("bathroom2", 1, 0),
    newFCNode("outside_lavatory", 1, 1)]

let h1LivingRooms = [
    newFCNode("lounge", 1, 0),
    newFCNode("dining_room", 1, 0),
    newFCNode("conservatory", 1, 0),
    newFCNode("playroom", 1, 1)]

let h2Upstairs = [
    newFCNode("bedrooms", 1, 0),
    newFCNode("bathroom", 1, 0),
    newFCNode("toilet", 1, 0),
    newFCNode("attics", 1, 0.6)]

let h2Groundfloor = [
    newFCNode("kitchen", 1, 0),
    newFCNode("living_rooms", 1, 0),
    newFCNode("wet_room_&_toilet", 1, 0),
    newFCNode("garage", 1, 0),
    newFCNode("garden", 1, 0.9),
    newFCNode("hot_tub_suite", 1, 1)]

let h2Basement = [
    newFCNode("cellars", 1, 1),
    newFCNode("wine_cellar", 1, 1),
    newFCNode("cinema", 1, 0.75)]

let h2UpstairsBedrooms = [
    newFCNode("suite_1", 1, 0),
    newFCNode("suite_2", 1, 0),
    newFCNode("bedroom_3", 1, 0),
    newFCNode("bedroom_4", 1, 0)]

let h2GroundfloorLivingRooms = [
    newFCNode("lounge", 1, 0),
    newFCNode("dining_room", 1, 0),
    newFCNode("conservatory", 1, 0),
    newFCNode("playroom", 1, 0)]

let cleaning = newFCNode("cleaning", 1, 0)

house1[1].addChildren(h1Bathrooms)
house1[4].addChildren(h1LivingRooms)
houses[0].addChildren(house1)

h2Upstairs[0].addChildren(h2UpstairsBedrooms)
house2[0].addChildren(h2Upstairs)
h2Groundfloor[1].addChildren(h2GroundfloorLivingRooms)
house2[1].addChildren(h2Groundfloor)
house2[2].addChildren(h2Basement)
houses[1].addChildren(house2)

cleaning.addChildren(houses)
let topCoverage = cleaning.coverage
echo &"TOP COVERAGE = {topCoverage:8.6f}\n"
echo "NAME HIERARCHY                 | WEIGHT | COVERAGE |"
cleaning.show(0)

h2Basement[2].setCoverage(1)  # Change Cinema node coverage to 1.
let diff = cleaning.coverage - topCoverage
echo "\nIf the coverage of the Cinema node were increased from 0.75 to 1"
echo &"the top level coverage would increase by {diff:8.6f} to {topCoverage + diff:8.6f}"
h2Basement[2].setCoverage(0.75)   # Restore to original value if required.
Output:
TOP COVERAGE = 0.409167

NAME HIERARCHY                 | WEIGHT | COVERAGE |
cleaning                       |    1   | 0.409167 |
    house1                     |   40   | 0.331250 |
        bedrooms               |    1   | 0.250000 |
        bathrooms              |    1   | 0.500000 |
            bathroom1          |    1   | 0.500000 |
            bathroom2          |    1   | 0.000000 |
            outside_lavatory   |    1   | 1.000000 |
        attic                  |    1   | 0.750000 |
        kitchen                |    1   | 0.100000 |
        living_rooms           |    1   | 0.250000 |
            lounge             |    1   | 0.000000 |
            dining_room        |    1   | 0.000000 |
            conservatory       |    1   | 0.000000 |
            playroom           |    1   | 1.000000 |
        basement               |    1   | 0.000000 |
        garage                 |    1   | 0.000000 |
        garden                 |    1   | 0.800000 |
    house2                     |   60   | 0.461111 |
        upstairs               |    1   | 0.150000 |
            bedrooms           |    1   | 0.000000 |
                suite_1        |    1   | 0.000000 |
                suite_2        |    1   | 0.000000 |
                bedroom_3      |    1   | 0.000000 |
                bedroom_4      |    1   | 0.000000 |
            bathroom           |    1   | 0.000000 |
            toilet             |    1   | 0.000000 |
            attics             |    1   | 0.600000 |
        groundfloor            |    1   | 0.316667 |
            kitchen            |    1   | 0.000000 |
            living_rooms       |    1   | 0.000000 |
                lounge         |    1   | 0.000000 |
                dining_room    |    1   | 0.000000 |
                conservatory   |    1   | 0.000000 |
                playroom       |    1   | 0.000000 |
            wet_room_&_toilet  |    1   | 0.000000 |
            garage             |    1   | 0.000000 |
            garden             |    1   | 0.900000 |
            hot_tub_suite      |    1   | 1.000000 |
        basement               |    1   | 0.916667 |
            cellars            |    1   | 1.000000 |
            wine_cellar        |    1   | 1.000000 |
            cinema             |    1   | 0.750000 |

If the coverage of the Cinema node were increased from 0.75 to 1
the top level coverage would increase by 0.016667 to 0.425833

Perl

use strict;
use warnings;

sub walktree {
    my @parts;
    while( $_[0] =~ /(?<head> (\s*)   \N+\n  )         # split off one level as 'head' (or terminal 'leaf')
                     (?<body> (?:\2 \s\N+\n)*)/gx ) {  # next sub-level is 'body' (defined by extra depth of indentation)

        my($head, $body) = ($+{head}, $+{body});
        $head =~ /^.*?      \|      # ignore name
                  (\S*) \s* \|      # save weight
                  (\S*)       /x;   # save coverage
        my $weight   = sprintf '%-8s',  $1 || 1;
        my $coverage = sprintf '%-10s', $2 || 0;
        my($w, $wsum) = (0, 0);

        $head .= $_->[0],
        $w    += $_->[1],
        $wsum += $_->[1] * $_->[2]
              for walktree( $body );

        $coverage = sprintf '%-10.2g', $wsum/$w unless $w == 0;
        push @parts, [ $head =~ s/\|.*/|$weight|$coverage|/r, $weight, $coverage ];
    }
    return @parts;
}

print $_->[0] for walktree( join '', <DATA> );

__DATA__
NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |        |          |
    house1                      |40      |          |
        bedrooms                |        |0.25      |
        bathrooms               |        |          |
            bathroom1           |        |0.5       |
            bathroom2           |        |          |
            outside_lavatory    |        |1         |
        attic                   |        |0.75      |
        kitchen                 |        |0.1       |
        living_rooms            |        |          |
            lounge              |        |          |
            dining_room         |        |          |
            conservatory        |        |          |
            playroom            |        |1         |
        basement                |        |          |
        garage                  |        |          |
        garden                  |        |0.8       |
    house2                      |60      |          |
        upstairs                |        |          |
            bedrooms            |        |          |
                suite_1         |        |          |
                suite_2         |        |          |
                bedroom_3       |        |          |
                bedroom_4       |        |          |
            bathroom            |        |          |
            toilet              |        |          |
            attics              |        |0.6       |
        groundfloor             |        |          |
            kitchen             |        |          |
            living_rooms        |        |          |
                lounge          |        |          |
                dining_room     |        |          |
                conservatory    |        |          |
                playroom        |        |          |
            wet_room_&_toilet   |        |          |
            garage              |        |          |
            garden              |        |0.9       |
            hot_tub_suite       |        |1         |
        basement                |        |          |
            cellars             |        |1         |
            wine_cellar         |        |1         |
            cinema              |        |0.75      |
Output:
NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |1       |0.41      |
    house1                      |40      |0.33      |
        bedrooms                |1       |0.25      |
        bathrooms               |1       |0.5       |
            bathroom1           |1       |0.5       |
            bathroom2           |1       |0         |
            outside_lavatory    |1       |1         |
        attic                   |1       |0.75      |
        kitchen                 |1       |0.1       |
        living_rooms            |1       |0.25      |
            lounge              |1       |0         |
            dining_room         |1       |0         |
            conservatory        |1       |0         |
            playroom            |1       |1         |
        basement                |1       |0         |
        garage                  |1       |0         |
        garden                  |1       |0.8       |
    house2                      |60      |0.46      |
        upstairs                |1       |0.15      |
            bedrooms            |1       |0         |
                suite_1         |1       |0         |
                suite_2         |1       |0         |
                bedroom_3       |1       |0         |
                bedroom_4       |1       |0         |
            bathroom            |1       |0         |
            toilet              |1       |0         |
            attics              |1       |0.6       |
        groundfloor             |1       |0.32      |
            kitchen             |1       |0         |
            living_rooms        |1       |0         |
                lounge          |1       |0         |
                dining_room     |1       |0         |
                conservatory    |1       |0         |
                playroom        |1       |0         |
            wet_room_&_toilet   |1       |0         |
            garage              |1       |0         |
            garden              |1       |0.9       |
            hot_tub_suite       |1       |1         |
        basement                |1       |0.92      |
            cellars             |1       |1         |
            wine_cellar         |1       |1         |
            cinema              |1       |0.75      |

Phix

--
-- demo\rosetta\Functional_coverage_tree.exw
-- =========================================
--
with javascript_semantics
constant data = """
NAME_HIERARCHY                  | WEIGHT | COVERAGE |
cleaning                        |        |          |
    house1                      |40      |          |
        bedrooms                |        |0.25      |
        bathrooms               |        |          |
            bathroom1           |        |0.5       |
            bathroom2           |        |          |
            outside_lavatory    |        |1         |
        attic                   |        |0.75      |
        kitchen                 |        |0.1       |
        living_rooms            |        |          |
            lounge              |        |          |
            dining_room         |        |          |
            conservatory        |        |          |
            playroom            |        |1         |
        basement                |        |          |
        garage                  |        |          |
        garden                  |        |0.8       |
    house2                      |60      |          |
        upstairs                |        |          |
            bedrooms            |        |          |
                suite_1         |        |          |
                suite_2         |        |          |
                bedroom_3       |        |          |
                bedroom_4       |        |          |
            bathroom            |        |          |
            toilet              |        |          |
            attics              |        |0.6       |
        groundfloor             |        |          |
            kitchen             |        |          |
            living_rooms        |        |          |
                lounge          |        |          |
                dining_room     |        |          |
                conservatory    |        |          |
                playroom        |        |          |
            wet_room_&_toilet   |        |          |
            garage              |        |          |
            garden              |        |0.9       |
            hot_tub_suite       |        |1         |
        basement                |        |          |
            cellars             |        |1         |
            wine_cellar         |        |1         |
            cinema              |        |0.75      |
"""
sequence lines = split(data,"\n"),
         pi = {},   -- indents (to locate parents)
         pdx = {},  -- indexes for ""
         children
string desc, weights, covers
integer parent, child
atom weight, coverage, childw = 0
enum DESC, WEIGHT, COVERAGE, PARENT, CHILDREN, CHILDW
lines[1] &= " SHARE OF RESIDUE"
for i=2 to length(lines) do
    -- decode text to useable data, link up parents & children
    {desc,weights,covers} = split(lines[i],"|")
    -- (nb this assumes /totally/ consistent indenting)
    integer indent = length(desc)-length(trim_head(desc)),
            k = find(indent,pi)
    if k=0 then
        pi = append(pi,indent)
        pdx = append(pdx,0)
        k = length(pi)
    end if
    pdx[k] = i
    parent = 0
    if k>1 then
        parent = pdx[k-1]
--      lines[parent][CHILDREN] &= i
        lines[parent][CHILDREN] = deep_copy(lines[parent][CHILDREN]) & i
    end if
    children = {}
    weight = to_number(trim(weights),1)
    coverage = to_number(trim(covers),0)
    lines[i] = {desc, weight, coverage, parent, children, childw}
end for     
for i=length(lines) to 2 by -1 do
    -- calculate the parent coverages, and save child weight sums
    children = lines[i][CHILDREN]
    if length(children) then
        coverage = 0
        childw = 0
        for c=1 to length(children) do
            child = children[c]
            atom w = lines[child][WEIGHT]
            coverage += lines[child][COVERAGE]*w
            childw += w
        end for
        lines[i][COVERAGE] = coverage/childw
        lines[i][CHILDW] = childw -- (save for next loop)
    end if
end for
for i=length(lines) to 2 by -1 do
    -- calculate the share of residue, and format lines
    child = i
    {desc, weight, coverage, parent} = lines[i]
    atom residue = 1-coverage
    while parent do
        residue *= lines[child][WEIGHT]/lines[parent][CHILDW]
        {child, parent} = {parent, lines[parent][PARENT]}
    end while
    lines[i] = sprintf("%-32s| %6d | %-8g | %g",{desc,weight,coverage,residue})
end for
puts(1,join(lines,"\n")&"\n")
{} = wait_key()
Output:
NAME_HIERARCHY                  | WEIGHT | COVERAGE | SHARE OF RESIDUE
cleaning                        |      1 | 0.409167 | 0.590833
    house1                      |     40 | 0.33125  | 0.2675
        bedrooms                |      1 | 0.25     | 0.0375
        bathrooms               |      1 | 0.5      | 0.025
            bathroom1           |      1 | 0.5      | 0.00833333
            bathroom2           |      1 | 0        | 0.0166667
            outside_lavatory    |      1 | 1        | 0
        attic                   |      1 | 0.75     | 0.0125
        kitchen                 |      1 | 0.1      | 0.045
        living_rooms            |      1 | 0.25     | 0.0375
            lounge              |      1 | 0        | 0.0125
            dining_room         |      1 | 0        | 0.0125
            conservatory        |      1 | 0        | 0.0125
            playroom            |      1 | 1        | 0
        basement                |      1 | 0        | 0.05
        garage                  |      1 | 0        | 0.05
        garden                  |      1 | 0.8      | 0.01
    house2                      |     60 | 0.461111 | 0.323333
        upstairs                |      1 | 0.15     | 0.17
            bedrooms            |      1 | 0        | 0.05
                suite_1         |      1 | 0        | 0.0125
                suite_2         |      1 | 0        | 0.0125
                bedroom_3       |      1 | 0        | 0.0125
                bedroom_4       |      1 | 0        | 0.0125
            bathroom            |      1 | 0        | 0.05
            toilet              |      1 | 0        | 0.05
            attics              |      1 | 0.6      | 0.02
        groundfloor             |      1 | 0.316667 | 0.136667
            kitchen             |      1 | 0        | 0.0333333
            living_rooms        |      1 | 0        | 0.0333333
                lounge          |      1 | 0        | 0.00833333
                dining_room     |      1 | 0        | 0.00833333
                conservatory    |      1 | 0        | 0.00833333
                playroom        |      1 | 0        | 0.00833333
            wet_room_&_toilet   |      1 | 0        | 0.0333333
            garage              |      1 | 0        | 0.0333333
            garden              |      1 | 0.9      | 0.00333333
            hot_tub_suite       |      1 | 1        | 0
        basement                |      1 | 0.916667 | 0.0166667
            cellars             |      1 | 1        | 0
            wine_cellar         |      1 | 1        | 0
            cinema              |      1 | 0.75     | 0.0166667

Python

Python: Using lists and tuples

It's actually some of the raw code used when researching this task.

from itertools import zip_longest


fc2 = '''\
cleaning,,
    house1,40,
        bedrooms,,.25
        bathrooms,,
            bathroom1,,.5
            bathroom2,,
            outside_lavatory,,1
        attic,,.75
        kitchen,,.1
        living_rooms,,
            lounge,,
            dining_room,,
            conservatory,,
            playroom,,1
        basement,,
        garage,,
        garden,,.8
    house2,60,
        upstairs,,
            bedrooms,,
                suite_1,,
                suite_2,,
                bedroom_3,,
                bedroom_4,,
            bathroom,,
            toilet,,
            attics,,.6
        groundfloor,,
            kitchen,,
            living_rooms,,
                lounge,,
                dining_room,,
                conservatory,,
                playroom,,
            wet_room_&_toilet,,
            garage,,
            garden,,.9
            hot_tub_suite,,1
        basement,,
            cellars,,1
            wine_cellar,,1
            cinema,,.75

'''

NAME, WT, COV = 0, 1, 2

def right_type(txt):
    try:
        return float(txt)
    except ValueError:
        return txt

def commas_to_list(the_list, lines, start_indent=0):
    '''
    Output format is a nest of lists and tuples
    lists are for coverage leaves without children items in the list are name, weight, coverage
    tuples are 2-tuples for nodes with children. The first element is a list representing the
    name, weight, coverage of the node (some to be calculated); the second element is a list of
    child elements which may be 2-tuples or lists as above.
    
    the_list is modified in-place
    lines must be a generator of successive lines of input like fc2
    '''
    for n, line in lines:
        indent = 0
        while line.startswith(' ' * (4 * indent)):
            indent += 1
        indent -= 1
        fields = [right_type(f) for f in line.strip().split(',')]
        if indent == start_indent:
            the_list.append(fields)
        elif indent > start_indent:
            lst = [fields]
            sub = commas_to_list(lst, lines, indent)
            the_list[-1] = (the_list[-1], lst)
            if sub not in (None, ['']) :
                the_list.append(sub)
        else:
            return fields if fields else None
    return None


def pptreefields(lst, indent=0, widths=['%-32s', '%-8g', '%-10g']):
    '''
    Pretty prints the format described from function commas_to_list as a table with 
    names in the first column suitably indented and all columns having a fixed 
    minimum column width.
    '''
    lhs = ' ' * (4 * indent)
    for item in lst:
        if type(item) != tuple:
            name, *rest = item
            print(widths[0] % (lhs + name), end='|')
            for width, item in zip_longest(widths[1:len(rest)], rest, fillvalue=widths[-1]):
                if type(item) == str:
                    width = width[:-1] + 's'
                print(width % item, end='|')
            print()
        else:
            item, children = item
            name, *rest = item
            print(widths[0] % (lhs + name), end='|')
            for width, item in zip_longest(widths[1:len(rest)], rest, fillvalue=widths[-1]):
                if type(item) == str:
                    width = width[:-1] + 's'
                print(width % item, end='|')
            print()
            pptreefields(children, indent+1)


def default_field(node_list):
    node_list[WT] = node_list[WT] if node_list[WT] else 1.0
    node_list[COV] = node_list[COV] if node_list[COV] else 0.0

def depth_first(tree, visitor=default_field):
    for item in tree:
        if type(item) == tuple:
            item, children = item
            depth_first(children, visitor)
        visitor(item)
            

def covercalc(tree):
    '''
    Depth first weighted average of coverage
    '''
    sum_covwt, sum_wt = 0, 0
    for item in tree:
        if type(item) == tuple:
            item, children = item
            item[COV] = covercalc(children)
        sum_wt  += item[WT]
        sum_covwt += item[COV] * item[WT]
    cov = sum_covwt / sum_wt
    return cov

if __name__ == '__main__':        
    lstc = []
    commas_to_list(lstc, ((n, ln) for n, ln in enumerate(fc2.split('\n'))))
    #pp(lstc, width=1, indent=4, compact=1)
    
    #print('\n\nEXPANDED DEFAULTS\n')
    depth_first(lstc)
    #pptreefields(['NAME_HIERARCHY WEIGHT COVERAGE'.split()] + lstc)
    
    print('\n\nTOP COVERAGE = %f\n' % covercalc(lstc))
    depth_first(lstc)
    pptreefields(['NAME_HIERARCHY WEIGHT COVERAGE'.split()] + lstc)
Output:
TOP COVERAGE = 0.409167

NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |1       |0.409167  |
    house1                      |40      |0.33125   |
        bedrooms                |1       |0.25      |
        bathrooms               |1       |0.5       |
            bathroom1           |1       |0.5       |
            bathroom2           |1       |0         |
            outside_lavatory    |1       |1         |
        attic                   |1       |0.75      |
        kitchen                 |1       |0.1       |
        living_rooms            |1       |0.25      |
            lounge              |1       |0         |
            dining_room         |1       |0         |
            conservatory        |1       |0         |
            playroom            |1       |1         |
        basement                |1       |0         |
        garage                  |1       |0         |
        garden                  |1       |0.8       |
    house2                      |60      |0.461111  |
        upstairs                |1       |0.15      |
            bedrooms            |1       |0         |
                suite_1         |1       |0         |
                suite_2         |1       |0         |
                bedroom_3       |1       |0         |
                bedroom_4       |1       |0         |
            bathroom            |1       |0         |
            toilet              |1       |0         |
            attics              |1       |0.6       |
        groundfloor             |1       |0.316667  |
            kitchen             |1       |0         |
            living_rooms        |1       |0         |
                lounge          |1       |0         |
                dining_room     |1       |0         |
                conservatory    |1       |0         |
                playroom        |1       |0         |
            wet_room_&_toilet   |1       |0         |
            garage              |1       |0         |
            garden              |1       |0.9       |
            hot_tub_suite       |1       |1         |
        basement                |1       |0.916667  |
            cellars             |1       |1         |
            wine_cellar         |1       |1         |
            cinema              |1       |0.75      |

Python: Class based and extra credit

A cleaner implementation that uses the class static variable path2node as in the previous example so you don't have to traverse the tree to work out the position to add new nodes. This relies on parent nodes appearing before their children which is the case in the order of the add_node calls.

# -*- coding: utf-8 -*-

SPACES = 4
class Node:
    path2node = {}
    
    def add_node(self, pathname, wt, cov):
        path2node = self.path2node
        path, name = pathname.strip().rsplit('/', 1)
        node = Node(name, wt, cov)
        path2node[pathname] = node
        path2node[path].child.append(node) # Link the tree

    def __init__(self, name="", wt=1, cov=0.0, child=None):
        if child is None:
            child = []
        self.name, self.wt, self.cov, self.child = name, wt, cov, child
        self.delta = None
        self.sum_wt = wt
        if name == "": 
            # designate the top of the tree
            self.path2node[name] = self
    
    
    def __repr__(self, indent=0):
        name, wt, cov, delta, child = (self.name, self.wt, self.cov, 
                                       self.delta, self.child)
        lhs = ' ' * (SPACES * indent) + "Node(%r," % name
        txt = '%-40s wt=%2g, cov=%-8.5g, delta=%-10s, child=[' \
              % (lhs, wt, cov, ('n/a' if delta is None else '%-10.7f' % delta))
        if not child:
            txt += (']),\n')
        else:
            txt += ('\n')
            for c in child:
                txt += c.__repr__(indent + 1)
            txt += (' ' * (SPACES * indent) + "]),\n")
        return txt

    def covercalc(self):
        '''
        Depth first weighted average of coverage
        '''
        child = self.child
        if not child:
            return self.cov
        sum_covwt, sum_wt = 0, 0
        for node in child:
            nwt = node.wt
            ncov = node.covercalc()
            sum_wt += nwt
            sum_covwt += ncov * nwt
        cov = sum_covwt / sum_wt
        self.sum_wt = sum_wt
        self.cov = cov
        return cov

    def deltacalc(self, power=1.0):
        '''
        Top down distribution of weighted residuals
        '''
        sum_wt = self.sum_wt
        self.delta = delta = (1 - self.cov) * power
        for node in self.child:
            node.deltacalc(power * node.wt / sum_wt)
        return delta


def isclose(a, b, rel_tol=1e-9, abs_tol=1e-9):
    return abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
    
    
if __name__ == '__main__': 
    top = Node()    # Add placeholder for top of tree
    add_node = top.add_node
    
    add_node('/cleaning', 1, 0)
    add_node('/cleaning/house1', 40, 0)
    add_node('/cleaning/house1/bedrooms', 1, 0.25)
    add_node('/cleaning/house1/bathrooms', 1, 0)
    add_node('/cleaning/house1/bathrooms/bathroom1', 1, 0.5)
    add_node('/cleaning/house1/bathrooms/bathroom2', 1, 0)
    add_node('/cleaning/house1/bathrooms/outside_lavatory', 1, 1)
    add_node('/cleaning/house1/attic', 1, 0.75)
    add_node('/cleaning/house1/kitchen', 1, 0.1)
    add_node('/cleaning/house1/living_rooms', 1, 0)
    add_node('/cleaning/house1/living_rooms/lounge', 1, 0)
    add_node('/cleaning/house1/living_rooms/dining_room', 1, 0)
    add_node('/cleaning/house1/living_rooms/conservatory', 1, 0)
    add_node('/cleaning/house1/living_rooms/playroom', 1, 1)
    add_node('/cleaning/house1/basement', 1, 0)
    add_node('/cleaning/house1/garage', 1, 0)
    add_node('/cleaning/house1/garden', 1, 0.8)
    add_node('/cleaning/house2', 60, 0)
    add_node('/cleaning/house2/upstairs', 1, 0)
    add_node('/cleaning/house2/upstairs/bedrooms', 1, 0)
    add_node('/cleaning/house2/upstairs/bedrooms/suite_1', 1, 0)
    add_node('/cleaning/house2/upstairs/bedrooms/suite_2', 1, 0)
    add_node('/cleaning/house2/upstairs/bedrooms/bedroom_3', 1, 0)
    add_node('/cleaning/house2/upstairs/bedrooms/bedroom_4', 1, 0)
    add_node('/cleaning/house2/upstairs/bathroom', 1, 0)
    add_node('/cleaning/house2/upstairs/toilet', 1, 0)
    add_node('/cleaning/house2/upstairs/attics', 1, 0.6)
    add_node('/cleaning/house2/groundfloor', 1, 0)
    add_node('/cleaning/house2/groundfloor/kitchen', 1, 0)
    add_node('/cleaning/house2/groundfloor/living_rooms', 1, 0)
    add_node('/cleaning/house2/groundfloor/living_rooms/lounge', 1, 0)
    add_node('/cleaning/house2/groundfloor/living_rooms/dining_room', 1, 0)
    add_node('/cleaning/house2/groundfloor/living_rooms/conservatory', 1, 0)
    add_node('/cleaning/house2/groundfloor/living_rooms/playroom', 1, 0)
    add_node('/cleaning/house2/groundfloor/wet_room_&_toilet', 1, 0)
    add_node('/cleaning/house2/groundfloor/garage', 1, 0)
    add_node('/cleaning/house2/groundfloor/garden', 1, 0.9)
    add_node('/cleaning/house2/groundfloor/hot_tub_suite', 1, 1)
    add_node('/cleaning/house2/basement', 1, 0)
    add_node('/cleaning/house2/basement/cellars', 1, 1)
    add_node('/cleaning/house2/basement/wine_cellar', 1, 1)
    add_node('/cleaning/house2/basement/cinema', 1, 0.75)

    top = top.child[0]  # Remove artificial top
    cover = top.covercalc()
    delta = top.deltacalc()
    print('TOP COVERAGE = %g\n' % cover)
    print(top)
    assert isclose((delta + cover), 1.0), "Top level delta + coverage should " \
                                          "equal 1 instead of (%f + %f)" % (delta, cover)
Output:

The deltas where checked by, for example, changing the coverage of the cinema in house2 to be 1.0 instead of 0.75 and observing an additional 0.0166667 increase in the top level coverage at node 'cleaning'.

TOP COVERAGE = 0.409167

Node('cleaning',                         wt= 1, cov=0.40917 , delta=0.5908333 , child=[
    Node('house1',                       wt=40, cov=0.33125 , delta=0.2675000 , child=[
        Node('bedrooms',                 wt= 1, cov=0.25    , delta=0.0375000 , child=[]),
        Node('bathrooms',                wt= 1, cov=0.5     , delta=0.0250000 , child=[
            Node('bathroom1',            wt= 1, cov=0.5     , delta=0.0083333 , child=[]),
            Node('bathroom2',            wt= 1, cov=0       , delta=0.0166667 , child=[]),
            Node('outside_lavatory',     wt= 1, cov=1       , delta=0.0000000 , child=[]),
        ]),
        Node('attic',                    wt= 1, cov=0.75    , delta=0.0125000 , child=[]),
        Node('kitchen',                  wt= 1, cov=0.1     , delta=0.0450000 , child=[]),
        Node('living_rooms',             wt= 1, cov=0.25    , delta=0.0375000 , child=[
            Node('lounge',               wt= 1, cov=0       , delta=0.0125000 , child=[]),
            Node('dining_room',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
            Node('conservatory',         wt= 1, cov=0       , delta=0.0125000 , child=[]),
            Node('playroom',             wt= 1, cov=1       , delta=0.0000000 , child=[]),
        ]),
        Node('basement',                 wt= 1, cov=0       , delta=0.0500000 , child=[]),
        Node('garage',                   wt= 1, cov=0       , delta=0.0500000 , child=[]),
        Node('garden',                   wt= 1, cov=0.8     , delta=0.0100000 , child=[]),
    ]),
    Node('house2',                       wt=60, cov=0.46111 , delta=0.3233333 , child=[
        Node('upstairs',                 wt= 1, cov=0.15    , delta=0.1700000 , child=[
            Node('bedrooms',             wt= 1, cov=0       , delta=0.0500000 , child=[
                Node('suite_1',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
                Node('suite_2',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
                Node('bedroom_3',        wt= 1, cov=0       , delta=0.0125000 , child=[]),
                Node('bedroom_4',        wt= 1, cov=0       , delta=0.0125000 , child=[]),
            ]),
            Node('bathroom',             wt= 1, cov=0       , delta=0.0500000 , child=[]),
            Node('toilet',               wt= 1, cov=0       , delta=0.0500000 , child=[]),
            Node('attics',               wt= 1, cov=0.6     , delta=0.0200000 , child=[]),
        ]),
        Node('groundfloor',              wt= 1, cov=0.31667 , delta=0.1366667 , child=[
            Node('kitchen',              wt= 1, cov=0       , delta=0.0333333 , child=[]),
            Node('living_rooms',         wt= 1, cov=0       , delta=0.0333333 , child=[
                Node('lounge',           wt= 1, cov=0       , delta=0.0083333 , child=[]),
                Node('dining_room',      wt= 1, cov=0       , delta=0.0083333 , child=[]),
                Node('conservatory',     wt= 1, cov=0       , delta=0.0083333 , child=[]),
                Node('playroom',         wt= 1, cov=0       , delta=0.0083333 , child=[]),
            ]),
            Node('wet_room_&_toilet',    wt= 1, cov=0       , delta=0.0333333 , child=[]),
            Node('garage',               wt= 1, cov=0       , delta=0.0333333 , child=[]),
            Node('garden',               wt= 1, cov=0.9     , delta=0.0033333 , child=[]),
            Node('hot_tub_suite',        wt= 1, cov=1       , delta=0.0000000 , child=[]),
        ]),
        Node('basement',                 wt= 1, cov=0.91667 , delta=0.0166667 , child=[
            Node('cellars',              wt= 1, cov=1       , delta=0.0000000 , child=[]),
            Node('wine_cellar',          wt= 1, cov=1       , delta=0.0000000 , child=[]),
            Node('cinema',               wt= 1, cov=0.75    , delta=0.0166667 , child=[]),
        ]),
    ]),
]),


Python: Composition of pure functions

Parsing the task statement text directly to a tree of dictionaries, folding a weightedTreeAverage function over that tree, and further decorating it with a top-down residue-share traversal.

Mainly uses pre-existing generic functions, including forestFromLineIndents, foldTree and fmapTree:

Works with: Python version 3.7
'''Functional coverage tree'''

from itertools import chain, product
from functools import reduce


# main :: IO ()
def main():
    '''Tabular outline serialisation of a parse tree
       decorated with computations of:
       1. Weighted coverage of each tree node.
       2. Each node's share of the total project's
          remaining work.
    '''
    columnWidths = [31, 9, 9, 9]
    delimiter = '|'

    reportLines = REPORT.splitlines()
    columnTitles = init(columnNames(delimiter)(reportLines[0]))

    # ------ SERIALISATION OF DECORATED PARSE TREE -------
    print(titleLine(delimiter)(columnWidths)(
        columnTitles + ['share of residue']
    ))
    print(indentedLinesFromTree('    ', tabulation(columnWidths))(

        # -------- TWO COMPUTATIONS BY TRAVERSAL ---------
        withResidueShares(1.0)(
            foldTree(weightedCoverage)(

                # --- TREE FROM PARSE OF OUTLINE TEXT ----
                fmapTree(
                    recordFromKeysDefaultsDelimiterAndLine(
                        columnTitles
                    )(
                        [str, float, float])([
                            '?', 1.0, 0.0
                        ])(delimiter)
                )(
                    forestFromIndentLevels(
                        indentLevelsFromLines(
                            reportLines[1:]
                        )
                    )[0]
                )
            )
        )
    ))


# ---- WEIGHTED COVERAGE, AND SHARE OF TOTAL RESIDUE -----

# weightedCoverage :: Tree Dict ->
# [Tree Dict] -> Tree Dict
def weightedCoverage(x):
    '''The weighted coverage of a tree node,
       as a function of the weighted averages
       of its children.
    '''
    def go(xs):
        cws = [
            (r['coverage'], r['weight']) for r
            in [root(x) for x in xs]
        ]
        totalWeight = reduce(lambda a, x: a + x[1], cws, 0)
        return Node(dict(
            x, **{
                'coverage': round(reduce(
                    lambda a, cw: a + (cw[0] * cw[1]),
                    cws, x['coverage']
                ) / (totalWeight if 0 < totalWeight else 1), 5)
            }
        ))(xs)
    return go


# withResidueShares :: Float -> Tree Dict -> Tree Dict
def withResidueShares(shareOfTotal):
    '''A Tree of dictionaries additionally decorated with each
       node's proportion of the total project's outstanding work.
    '''
    def go(fraction, node):
        [nodeRoot, nodeNest] = ap([root, nest])([node])
        weights = [root(x)['weight'] for x in nodeNest]
        siblingsTotal = sum(weights)
        return Node(
            insertDict('residual_share')(
                round(fraction * (1 - nodeRoot['coverage']), 5)
            )(nodeRoot)
        )(
            map(
                go,
                [fraction * (w / siblingsTotal) for w in weights],
                nodeNest
            )
        )
    return lambda tree: go(shareOfTotal, tree)


# ------------------ OUTLINE TABULATION ------------------

# tabulation :: [Int] -> String -> Dict -> String
def tabulation(columnWidths):
    '''Indented string representation of a node
       in a functional coverage tree.
    '''
    return lambda indent, dct: '| '.join(map(
        lambda k, w: (
            (indent if 10 < w else '') + str(dct.get(k, ''))
        ).ljust(w, ' '),
        dct.keys(),
        columnWidths
    ))


# titleLine :: String -> [Int] -> [String] -> String
def titleLine(delimiter):
    '''A string consisting of a spaced and delimited
       series of upper-case column titles.
    '''
    return lambda columnWidths: lambda ks: (
        delimiter + ' '
    ).join(map(
        lambda k, w: k.ljust(w, ' '),
        [k.upper() for k in ks],
        columnWidths
    ))


# ------------ GENERIC AND REUSABLE FUNCTIONS ------------

# Node :: a -> [Tree a] -> Tree a
def Node(v):
    '''Constructor for a Tree node which connects a
       value of some kind to a list of zero or
       more child trees.
    '''
    return lambda xs: {'type': 'Tree', 'root': v, 'nest': xs}


# ap (<*>) :: [(a -> b)] -> [a] -> [b]
def ap(fs):
    '''The application of each of a list of functions,
       to each of a list of values.
    '''
    def go(xs):
        return [
            f(x) for (f, x)
            in product(fs, xs)
        ]
    return go


# columnNames :: String -> String -> [String]
def columnNames(delimiter):
    '''A list of lower-case keys derived from
       a header line and a delimiter character.
    '''
    return compose(
        fmapList(compose(toLower, strip)),
        splitOn(delimiter)
    )


# compose :: ((a -> a), ...) -> (a -> a)
def compose(*fs):
    '''Composition, from right to left,
       of a series of functions.
    '''
    return lambda x: reduce(
        lambda a, f: f(a),
        fs[::-1], x
    )


# concatMap :: (a -> [b]) -> [a] -> [b]
def concatMap(f):
    '''A concatenated list over which a function has been mapped.
       The list monad can be derived by using a function f which
       wraps its output in a list,
       (using an empty list to represent computational failure).
    '''
    def go(xs):
        return chain.from_iterable(map(f, xs))
    return go


# div :: Int -> Int -> Int
def div(x):
    '''Integer division.'''
    return lambda y: x // y


# first :: (a -> b) -> ((a, c) -> (b, c))
def first(f):
    '''A simple function lifted to a function over a tuple,
       with f applied only the first of two values.
    '''
    return lambda xy: (f(xy[0]), xy[1])


# flip :: (a -> b -> c) -> b -> a -> c
def flip(f):
    '''The (curried or uncurried) function f with its
       arguments reversed.
    '''
    return lambda a: lambda b: f(b)(a)


# fmapList :: (a -> b) -> [a] -> [b]
def fmapList(f):
    '''fmap over a list.
       f lifted to a function over a list.
    '''
    return lambda xs: [f(x) for x in xs]


# fmapTree :: (a -> b) -> Tree a -> Tree b
def fmapTree(f):
    '''A new tree holding the results of
       an application of f to each root in
       the existing tree.
    '''
    def go(x):
        return Node(
            f(x['root'])
        )([go(v) for v in x['nest']])
    return go


# foldTree :: (a -> [b] -> b) -> Tree a -> b
def foldTree(f):
    '''The catamorphism on trees. A summary
       value defined by a depth-first fold.
    '''
    def go(node):
        return f(root(node))([
            go(x) for x in nest(node)
        ])
    return go


# forestFromIndentLevels :: [(Int, a)] -> [Tree a]
def forestFromIndentLevels(tuples):
    '''A list of trees derived from a list of values paired
       with integers giving their levels of indentation.
    '''
    def go(xs):
        if xs:
            intIndent, v = xs[0]
            firstTreeLines, rest = span(
                lambda x: intIndent < x[0]
            )(xs[1:])
            return [Node(v)(go(firstTreeLines))] + go(rest)
        else:
            return []
    return go(tuples)


# fst :: (a, b) -> a
def fst(tpl):
    '''First member of a pair.'''
    return tpl[0]


# indentLevelsFromLines :: [String] -> [(Int, String)]
def indentLevelsFromLines(xs):
    '''Each input line stripped of leading
       white space, and tupled with a preceding integer
       giving its level of indentation from 0 upwards.
    '''
    indentTextPairs = list(map(
        compose(first(len), span(isSpace)),
        xs
    ))
    indentUnit = min(concatMap(
        lambda x: [x[0]] if x[0] else []
    )(indentTextPairs))
    return list(map(
        first(flip(div)(indentUnit)),
        indentTextPairs
    ))


# indentedLinesFromTree :: String -> (String -> a -> String) ->
# [Tree a] -> String
def indentedLinesFromTree(strTab, f):
    '''An indented line rendering of a tree, in which
       the function f stringifies a root value.
    '''
    def go(indent):
        return lambda node: [f(indent, node['root'])] + list(
            concatMap(
                go(strTab + indent)
            )(node['nest'])
        )
    return lambda tree: '\n'.join(go('')(tree))


# init :: [a] -> [a]
def init(xs):
    '''A list containing all the elements
       of xs except the last.
    '''
    return xs[:-1]


# insertDict :: String -> a -> Dict -> Dict
def insertDict(k):
    '''A new dictionary updated with a (k, v) pair.'''
    def go(v, dct):
        return dict(dct, **{k: v})
    return lambda v: lambda dct: go(v, dct)


# isSpace :: Char -> Bool
# isSpace :: String -> Bool
def isSpace(s):
    '''True if s is not empty, and
       contains only white space.
    '''
    return s.isspace()


# lt (<) :: Ord a => a -> a -> Bool
def lt(x):
    '''True if x < y.'''
    return lambda y: (x < y)


# nest :: Tree a -> [Tree a]
def nest(t):
    '''Accessor function for children of tree node.'''
    return t['nest'] if 'nest' in t else None


# recordFromKeysDefaultsAndLine :: String ->
# { name :: String, weight :: Float, completion :: Float }
def recordFromKeysDefaultsDelimiterAndLine(columnTitles):
    '''A dictionary of key-value pairs, derived from a
       delimited string, together with ordered lists of
       key-names, types, default values, and a delimiter.
    '''
    return lambda ts: lambda vs: lambda delim: lambda s: dict(
        map(
            lambda k, t, v, x: (k, t(x) if x else v),
            columnTitles, ts, vs,
            map(strip, splitOn(delim)(s))
        )
    )


# root :: Tree a -> a
def root(t):
    '''Accessor function for data of tree node.'''
    return t['root'] if 'root' in t else None


# strip :: String -> String
def strip(s):
    '''A copy of s without any leading or trailling
       white space.
    '''
    return s.strip()


# span :: (a -> Bool) -> [a] -> ([a], [a])
def span(p):
    '''The longest (possibly empty) prefix of xs
       that contains only elements satisfying p,
       tupled with the remainder of xs.
       span p xs is equivalent to (takeWhile p xs, dropWhile p xs).
    '''
    def match(ab):
        b = ab[1]
        return not b or not p(b[0])

    def f(ab):
        a, b = ab
        return a + [b[0]], b[1:]

    def go(xs):
        return until(match)(f)(([], xs))
    return go


# splitOn :: String -> String -> [String]
def splitOn(pat):
    '''A list of the strings delimited by
       instances of a given pattern in s.
    '''
    return lambda xs: (
        xs.split(pat) if isinstance(xs, str) else None
    )


# toLower :: String -> String
def toLower(s):
    '''String in lower case.'''
    return s.lower()


# until :: (a -> Bool) -> (a -> a) -> a -> a
def until(p):
    '''The result of repeatedly applying f until p holds.
       The initial seed value is x.
    '''
    def go(f):
        def g(x):
            v = x
            while not p(v):
                v = f(v)
            return v
        return g
    return go


# MAIN ----------------------------------------------------
if __name__ == '__main__':
    REPORT = '''NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
    cleaning                        |        |          |
        house1                      |40      |          |
            bedrooms                |        |0.25      |
            bathrooms               |        |          |
                bathroom1           |        |0.5       |
                bathroom2           |        |          |
                outside_lavatory    |        |1         |
            attic                   |        |0.75      |
            kitchen                 |        |0.1       |
            living_rooms            |        |          |
                lounge              |        |          |
                dining_room         |        |          |
                conservatory        |        |          |
                playroom            |        |1         |
            basement                |        |          |
            garage                  |        |          |
            garden                  |        |0.8       |
        house2                      |60      |          |
            upstairs                |        |          |
                bedrooms            |        |          |
                    suite_1         |        |          |
                    suite_2         |        |          |
                    bedroom_3       |        |          |
                    bedroom_4       |        |          |
                bathroom            |        |          |
                toilet              |        |          |
                attics              |        |0.6       |
            groundfloor             |        |          |
                kitchen             |        |          |
                living_rooms        |        |          |
                    lounge          |        |          |
                    dining_room     |        |          |
                    conservatory    |        |          |
                    playroom        |        |          |
                wet_room_&_toilet   |        |          |
                garage              |        |          |
                garden              |        |0.9       |
                hot_tub_suite       |        |1         |
            basement                |        |          |
                cellars             |        |1         |
                wine_cellar         |        |1         |
                cinema              |        |0.75      |'''
    main()
Output:
NAME_HIERARCHY                 | WEIGHT   | COVERAGE | SHARE OF RESIDUE
cleaning                       | 1.0      | 0.40917  | 0.59083  
    house1                     | 40.0     | 0.33125  | 0.2675   
        bedrooms               | 1.0      | 0.25     | 0.0375   
        bathrooms              | 1.0      | 0.5      | 0.025    
            bathroom1          | 1.0      | 0.5      | 0.00833  
            bathroom2          | 1.0      | 0.0      | 0.01667  
            outside_lavatory   | 1.0      | 1.0      | 0.0      
        attic                  | 1.0      | 0.75     | 0.0125   
        kitchen                | 1.0      | 0.1      | 0.045    
        living_rooms           | 1.0      | 0.25     | 0.0375   
            lounge             | 1.0      | 0.0      | 0.0125   
            dining_room        | 1.0      | 0.0      | 0.0125   
            conservatory       | 1.0      | 0.0      | 0.0125   
            playroom           | 1.0      | 1.0      | 0.0      
        basement               | 1.0      | 0.0      | 0.05     
        garage                 | 1.0      | 0.0      | 0.05     
        garden                 | 1.0      | 0.8      | 0.01     
    house2                     | 60.0     | 0.46111  | 0.32333  
        upstairs               | 1.0      | 0.15     | 0.17     
            bedrooms           | 1.0      | 0.0      | 0.05     
                suite_1        | 1.0      | 0.0      | 0.0125   
                suite_2        | 1.0      | 0.0      | 0.0125   
                bedroom_3      | 1.0      | 0.0      | 0.0125   
                bedroom_4      | 1.0      | 0.0      | 0.0125   
            bathroom           | 1.0      | 0.0      | 0.05     
            toilet             | 1.0      | 0.0      | 0.05     
            attics             | 1.0      | 0.6      | 0.02     
        groundfloor            | 1.0      | 0.31667  | 0.13667  
            kitchen            | 1.0      | 0.0      | 0.03333  
            living_rooms       | 1.0      | 0.0      | 0.03333  
                lounge         | 1.0      | 0.0      | 0.00833  
                dining_room    | 1.0      | 0.0      | 0.00833  
                conservatory   | 1.0      | 0.0      | 0.00833  
                playroom       | 1.0      | 0.0      | 0.00833  
            wet_room_&_toilet  | 1.0      | 0.0      | 0.03333  
            garage             | 1.0      | 0.0      | 0.03333  
            garden             | 1.0      | 0.9      | 0.00333  
            hot_tub_suite      | 1.0      | 1.0      | 0.0      
        basement               | 1.0      | 0.91667  | 0.01667  
            cellars            | 1.0      | 1.0      | 0.0      
            wine_cellar        | 1.0      | 1.0      | 0.0      
            cinema             | 1.0      | 0.75     | 0.01667 

Racket

To save on paper, the coverage table needs to be saved to a file (in this case data/functional-coverage.txt).

#lang racket/base
(require racket/list racket/string racket/match racket/format racket/file)

(struct Coverage (name weight coverage weighted-coverage children) #:transparent #:mutable)

;; -| read/parse |------------------------------------------------------------------------------------
(define (build-hierarchies parsed-lines)
  (define inr
    (match-lambda
      ['() (values null null)]
      [`((,head-indent . ,C) ,tail-lines ...)
       (define child? (match-lambda [(cons i _) #:when (> i head-indent) #t] [_ #f]))
       (define-values (chlds rels) (splitf-at tail-lines child?))
       (define-values (rels-tree rels-rem) (inr rels))
       (values (cons (struct-copy Coverage C (children (build-hierarchies chlds))) rels-tree)
               rels-rem)]))
  (define-values (hierarchies remaining-lines) (inr parsed-lines))
  hierarchies)

(define report-line->indent.c/e-line
  (match-lambda
    [(regexp #px"^( *)([^ ]*) *\\| *([^ ]*) *\\| *([^ ]*) *\\|$"
             (list _
                   (app string-length indent-length)
                   name
                   (or (and (not "") (app string->number wght)) (app (λ (x) 1) wght))
                   (or (and (not "") (app string->number cvrg)) (app (λ (x) 0) cvrg))))
     (cons indent-length (Coverage name wght cvrg 0 #f))]))

(define (report->indent.c/e-list rprt)
  (map report-line->indent.c/e-line (drop (string-split rprt "\n") 1)))

;; -| evaluate |--------------------------------------------------------------------------------------
(define find-wght-cvrg
  (match-lambda
    [(and e (Coverage _ w c _ '())) (struct-copy Coverage e (weighted-coverage (* w c)))]
    [(and e (Coverage _ _ _ _ `(,(app find-wght-cvrg (and cdn+ (Coverage _ c-ws _ c-w/cs _))) ...)))
     (define chld-wghtd-avg (for/sum ((w (in-list c-ws)) (w/c (in-list c-w/cs))) (* w w/c)))
     (struct-copy Coverage e (weighted-coverage (/ chld-wghtd-avg (apply + c-ws))) (children cdn+))]))

;; -| printing |--------------------------------------------------------------------------------------
(define max-description-length
  (match-lambda
    [(Coverage (app string-length name-length) _ _ _
               (list (app max-description-length children-lengths) ...))
     (apply max name-length (map add1 children-lengths))]))

(define (~a/right w x)
  (~a x #:width w #:align 'right))

(define (~a/decimal n dec-dgts)
  (~a/right (+ dec-dgts 3) (if (zero? n) "" (real->decimal-string n dec-dgts))))

(define (print-coverage-tree tree)
  (define mdl (max-description-length tree))
  (printf "| ~a |WEIGT| COVER |WGHTD CVRG|~%" (~a "NAME" #:width mdl #:align 'center))
  (let inr ((depth 0) (tree tree))
    (unless (null? tree)
      (match tree
        [(Coverage name w c w/c chlds)
         (printf "| ~a | ~a | ~a | ~a |~%"
                 (~a (string-append (make-string depth #\space) name) #:width mdl)
                 (~a/right 3 w) (~a/decimal c 2) (~a/decimal w/c 5))
         (for ((c chlds)) (inr (add1 depth) c))]))))

;; ---------------------------------------------------------------------------------------------------
(module+ main
;; data/functional-coverage.txt contains a verbatim copy of
;; the table in the task's description
(for-each
 (compose print-coverage-tree find-wght-cvrg)
 (build-hierarchies (report->indent.c/e-list (file->string "data/functional-coverage.txt")))))
Output:
|         NAME         |WEIGT| COVER |WGHTD CVRG|
| cleaning             |   1 |       |  0.40917 |
|  house1              |  40 |       |  0.33125 |
|   bedrooms           |   1 |  0.25 |  0.25000 |
|   bathrooms          |   1 |       |  0.50000 |
|    bathroom1         |   1 |  0.50 |  0.50000 |
|    bathroom2         |   1 |       |          |
|    outside_lavatory  |   1 |  1.00 |  1.00000 |
|   attic              |   1 |  0.75 |  0.75000 |
|   kitchen            |   1 |  0.10 |  0.10000 |
|   living_rooms       |   1 |       |  0.25000 |
|    lounge            |   1 |       |          |
|    dining_room       |   1 |       |          |
|    conservatory      |   1 |       |          |
|    playroom          |   1 |  1.00 |  1.00000 |
|   basement           |   1 |       |          |
|   garage             |   1 |       |          |
|   garden             |   1 |  0.80 |  0.80000 |
|  house2              |  60 |       |  0.46111 |
|   upstairs           |   1 |       |  0.15000 |
|    bedrooms          |   1 |       |          |
|     suite_1          |   1 |       |          |
|     suite_2          |   1 |       |          |
|     bedroom_3        |   1 |       |          |
|     bedroom_4        |   1 |       |          |
|    bathroom          |   1 |       |          |
|    toilet            |   1 |       |          |
|    attics            |   1 |  0.60 |  0.60000 |
|   groundfloor        |   1 |       |  0.31667 |
|    kitchen           |   1 |       |          |
|    living_rooms      |   1 |       |          |
|     lounge           |   1 |       |          |
|     dining_room      |   1 |       |          |
|     conservatory     |   1 |       |          |
|     playroom         |   1 |       |          |
|    wet_room_&_toilet |   1 |       |          |
|    garage            |   1 |       |          |
|    garden            |   1 |  0.90 |  0.90000 |
|    hot_tub_suite     |   1 |  1.00 |  1.00000 |
|   basement           |   1 |       |  0.91667 |
|    cellars           |   1 |  1.00 |  1.00000 |
|    wine_cellar       |   1 |  1.00 |  1.00000 |
|    cinema            |   1 |  0.75 |  0.75000 |

Raku

(formerly Perl 6)

Translation of: Perl
sub walktree ($data) {
    my (@parts, $cnt);

    while ($data ~~ m:nth(++$cnt)/$<head>=[(\s*)    \N+\n  ]      # split off one level as 'head' (or terminal 'leaf')
                                  $<body>=[[$0  \s+ \N+\n]*]/ ) { # next sub-level is 'body' (defined by extra depth of indentation)

        my ($head, $body) = ($<head>, $<body>);
        $head ~~ /'|' $<weight>=[\S*] \s* '|' $<coverage>=[\S*]/; # save values of weight and coverage (if any) for later

        my ($w, $wsum) = (0, 0);
        $head ~= .[0],
        $w    += .[1],
        $wsum += .[1] * .[2]
            for walktree $body;

        my $weight   = (~$<weight>   or 1).fmt('%-8s');
        my $coverage = $w == 0
                    ?? (~$<coverage> or 0).fmt('%-10s')
                    !! ($wsum/$w)         .fmt('%-10.2g');
        @parts.push: [$head.subst(/'|' \N+/, "|$weight|$coverage|"), $weight, $coverage ];
    }
    return @parts;
}

(say .[0] for walktree $_) given 

    q:to/END/
    NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
    cleaning                        |        |          |
        house1                      |40      |          |
            bedrooms                |        |0.25      |
            bathrooms               |        |          |
                bathroom1           |        |0.5       |
                bathroom2           |        |          |
                outside_lavatory    |        |1         |
            attic                   |        |0.75      |
            kitchen                 |        |0.1       |
            living_rooms            |        |          |
                lounge              |        |          |
                dining_room         |        |          |
                conservatory        |        |          |
                playroom            |        |1         |
            basement                |        |          |
            garage                  |        |          |
            garden                  |        |0.8       |
        house2                      |60      |          |
            upstairs                |        |          |
                bedrooms            |        |          |
                    suite_1         |        |          |
                    suite_2         |        |          |
                    bedroom_3       |        |          |
                    bedroom_4       |        |          |
                bathroom            |        |          |
                toilet              |        |          |
                attics              |        |0.6       |
            groundfloor             |        |          |
                kitchen             |        |          |
                living_rooms        |        |          |
                    lounge          |        |          |
                    dining_room     |        |          |
                    conservatory    |        |          |
                    playroom        |        |          |
                wet_room_&_toilet   |        |          |
                garage              |        |          |
                garden              |        |0.9       |
                hot_tub_suite       |        |1         |
            basement                |        |          |
                cellars             |        |1         |
                wine_cellar         |        |1         |
                cinema              |        |0.75      |
    END
Output:
NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |

cleaning                        |1       |0.41      |
    house1                      |40      |0.33      |
        bedrooms                |1       |0.25      |
        bathrooms               |1       |0.5       |
            bathroom1           |1       |0.5       |
            bathroom2           |1       |0         |
            outside_lavatory    |1       |1         |
        attic                   |1       |0.75      |
        kitchen                 |1       |0.1       |
        living_rooms            |1       |0.25      |
            lounge              |1       |0         |
            dining_room         |1       |0         |
            conservatory        |1       |0         |
            playroom            |1       |1         |
        basement                |1       |0         |
        garage                  |1       |0         |
        garden                  |1       |0.8       |
    house2                      |60      |0.46      |
        upstairs                |1       |0.15      |
            bedrooms            |1       |0         |
                suite_1         |1       |0         |
                suite_2         |1       |0         |
                bedroom_3       |1       |0         |
                bedroom_4       |1       |0         |
            bathroom            |1       |0         |
            toilet              |1       |0         |
            attics              |1       |0.6       |
        groundfloor             |1       |0.32      |
            kitchen             |1       |0         |
            living_rooms        |1       |0         |
                lounge          |1       |0         |
                dining_room     |1       |0         |
                conservatory    |1       |0         |
                playroom        |1       |0         |
            wet_room_&_toilet   |1       |0         |
            garage              |1       |0         |
            garden              |1       |0.9       |
            hot_tub_suite       |1       |1         |
        basement                |1       |0.92      |
            cellars             |1       |1         |
            wine_cellar         |1       |1         |
            cinema              |1       |0.75      |

Swift

Translation of: Kotlin
import Foundation

extension String {
  func paddedLeft(totalLen: Int) -> String {
    let needed = totalLen - count

    guard needed > 0 else {
      return self
    }

    return String(repeating: " ", count: needed) + self
  }
}

class FCNode {
  let name: String
  let weight: Int

  var coverage: Double {
    didSet {
      if oldValue != coverage {
        parent?.updateCoverage()
      }
    }
  }

  weak var parent: FCNode?
  var children = [FCNode]()

  init(name: String, weight: Int = 1, coverage: Double = 0) {
    self.name = name
    self.weight = weight
    self.coverage = coverage
  }

  func addChildren(_ children: [FCNode]) {
    for child in children {
      child.parent = self
    }

    self.children += children

    updateCoverage()
  }

  func show(level: Int = 0) {
    let indent = level * 4
    let nameLen = name.count + indent

    print(name.paddedLeft(totalLen: nameLen), terminator: "")
    print("|".paddedLeft(totalLen: 32 - nameLen), terminator: "")
    print(String(format: "  %3d   |", weight), terminator: "")
    print(String(format: " %8.6f |", coverage))
    
    for child in children {
      child.show(level: level + 1)
    }
  }

  func updateCoverage() {
    let v1 = children.reduce(0.0, { $0 + $1.coverage * Double($1.weight) })
    let v2 = children.reduce(0.0, { $0 + Double($1.weight) })

    coverage = v1 / v2
  }
}

let houses = [
  FCNode(name: "house1", weight: 40),
  FCNode(name: "house2", weight: 60)
]

let house1 = [
  FCNode(name: "bedrooms", weight: 1, coverage: 0.25),
  FCNode(name: "bathrooms"),
  FCNode(name: "attic", weight: 1, coverage: 0.75),
  FCNode(name: "kitchen", weight: 1, coverage: 0.1),
  FCNode(name: "living_rooms"),
  FCNode(name: "basement"),
  FCNode(name: "garage"),
  FCNode(name: "garden", weight: 1, coverage: 0.8)
]

let house2 = [
  FCNode(name: "upstairs"),
  FCNode(name: "groundfloor"),
  FCNode(name: "basement")
]

let h1Bathrooms = [
  FCNode(name: "bathroom1", weight: 1, coverage: 0.5),
  FCNode(name: "bathroom2"),
  FCNode(name: "outside_lavatory", weight: 1, coverage: 1.0)
]

let h1LivingRooms = [
  FCNode(name: "lounge"),
  FCNode(name: "dining_room"),
  FCNode(name: "conservatory"),
  FCNode(name: "playroom", weight: 1, coverage: 1.0)
]

let h2Upstairs = [
  FCNode(name: "bedrooms"),
  FCNode(name: "bathroom"),
  FCNode(name: "toilet"),
  FCNode(name: "attics", weight: 1, coverage: 0.6)
]

let h2Groundfloor = [
  FCNode(name: "kitchen"),
  FCNode(name: "living_rooms"),
  FCNode(name: "wet_room_&_toilet"),
  FCNode(name: "garage"),
  FCNode(name: "garden", weight: 1, coverage: 0.9),
  FCNode(name: "hot_tub_suite", weight: 1, coverage: 1.0)
]

let h2Basement = [
  FCNode(name: "cellars", weight: 1, coverage: 1.0),
  FCNode(name: "wine_cellar", weight: 1, coverage: 1.0),
  FCNode(name: "cinema", weight: 1, coverage: 0.75)
]

let h2UpstairsBedrooms = [
  FCNode(name: "suite_1"),
  FCNode(name: "suite_2"),
  FCNode(name: "bedroom_3"),
  FCNode(name: "bedroom_4")
]

let h2GroundfloorLivingRooms = [
  FCNode(name: "lounge"),
  FCNode(name: "dining_room"),
  FCNode(name: "conservatory"),
  FCNode(name: "playroom")
]

let cleaning = FCNode(name: "cleaning")

house1[1].addChildren(h1Bathrooms)
house1[4].addChildren(h1LivingRooms)
houses[0].addChildren(house1)

h2Upstairs[0].addChildren(h2UpstairsBedrooms)
house2[0].addChildren(h2Upstairs)
h2Groundfloor[1].addChildren(h2GroundfloorLivingRooms)
house2[1].addChildren(h2Groundfloor)
house2[2].addChildren(h2Basement)
houses[1].addChildren(house2)

cleaning.addChildren(houses)

let top = cleaning.coverage

print("Top Coverage: \(String(format: "%8.6f", top))")
print("Name Hierarchy                 | Weight | Coverage |")

cleaning.show()

h2Basement[2].coverage = 1.0

let diff = cleaning.coverage - top

print("\nIf the coverage of the Cinema node were increased from 0.75 to 1.0")
print("the top level coverage would increase by ")
print("\(String(format: "%8.6f", diff)) to \(String(format: "%8.6f", top))")
Output:
Top Coverage: 0.409167
Name Hierarchy                 | Weight | Coverage |
cleaning                       |    1   | 0.409167 |
    house1                     |   40   | 0.331250 |
        bedrooms               |    1   | 0.250000 |
        bathrooms              |    1   | 0.500000 |
            bathroom1          |    1   | 0.500000 |
            bathroom2          |    1   | 0.000000 |
            outside_lavatory   |    1   | 1.000000 |
        attic                  |    1   | 0.750000 |
        kitchen                |    1   | 0.100000 |
        living_rooms           |    1   | 0.250000 |
            lounge             |    1   | 0.000000 |
            dining_room        |    1   | 0.000000 |
            conservatory       |    1   | 0.000000 |
            playroom           |    1   | 1.000000 |
        basement               |    1   | 0.000000 |
        garage                 |    1   | 0.000000 |
        garden                 |    1   | 0.800000 |
    house2                     |   60   | 0.461111 |
        upstairs               |    1   | 0.150000 |
            bedrooms           |    1   | 0.000000 |
                suite_1        |    1   | 0.000000 |
                suite_2        |    1   | 0.000000 |
                bedroom_3      |    1   | 0.000000 |
                bedroom_4      |    1   | 0.000000 |
            bathroom           |    1   | 0.000000 |
            toilet             |    1   | 0.000000 |
            attics             |    1   | 0.600000 |
        groundfloor            |    1   | 0.316667 |
            kitchen            |    1   | 0.000000 |
            living_rooms       |    1   | 0.000000 |
                lounge         |    1   | 0.000000 |
                dining_room    |    1   | 0.000000 |
                conservatory   |    1   | 0.000000 |
                playroom       |    1   | 0.000000 |
            wet_room_&_toilet  |    1   | 0.000000 |
            garage             |    1   | 0.000000 |
            garden             |    1   | 0.900000 |
            hot_tub_suite      |    1   | 1.000000 |
        basement               |    1   | 0.916667 |
            cellars            |    1   | 1.000000 |
            wine_cellar        |    1   | 1.000000 |
            cinema             |    1   | 0.750000 |

If the coverage of the Cinema node were increased from 0.75 to 1.0
the top level coverage would increase by 
0.016667 to 0.409167

Wren

Translation of: Kotlin
Library: Wren-fmt
import "./fmt" for Fmt

class FCNode {
    construct new(name, weight, coverage) {
        _name = name
        _weight = weight
        _coverage = coverage
        _children = []
        _parent = null
    }

    static new(name, weight) { new(name, weight, 0) }
    static new(name)         { new(name, 1, 0) }

    name     { _name }
    weight   { _weight }
    coverage { _coverage }

    coverage=(value) {
        if (_coverage != value) {
            _coverage = value
            if (_parent) {
                _parent.updateCoverage_() // update any parent's coverage
            }
        }
    }

    parent     { _parent }
    parent=(p) { _parent = p }

    addChildren(nodes) {
        _children.addAll(nodes)
        for (node in nodes) node.parent = this
        updateCoverage_()
    }

    updateCoverage_() {
        var v1 = _children.reduce(0) { |acc, n| acc + n.weight * n.coverage }
        var v2 = _children.reduce(0) { |acc, n| acc + n.weight }
        coverage = v1 / v2
    }

    show(level) {
        var indent = level * 4
        var nl = _name.count + indent
        Fmt.lprint("$*s$*s  $3d   | $8.6f |", [nl, _name, 32-nl, "|", _weight, _coverage])
        if (_children.isEmpty) return
        for (child in _children) child.show(level+1)
    }
}

var houses = [
    FCNode.new("house1", 40),
    FCNode.new("house2", 60)
]

var house1 = [
    FCNode.new("bedrooms", 1, 0.25),
    FCNode.new("bathrooms"),
    FCNode.new("attic", 1, 0.75),
    FCNode.new("kitchen", 1, 0.1),
    FCNode.new("living_rooms"),
    FCNode.new("basement"),
    FCNode.new("garage"),
    FCNode.new("garden", 1, 0.8)
]

var house2 = [
    FCNode.new("upstairs"),
    FCNode.new("groundfloor"),
    FCNode.new("basement")
]

var h1Bathrooms = [
    FCNode.new("bathroom1", 1, 0.5),
    FCNode.new("bathroom2"),
    FCNode.new("outside_lavatory", 1, 1)
]

var h1LivingRooms = [
    FCNode.new("lounge"),
    FCNode.new("dining_room"),
    FCNode.new("conservatory"),
    FCNode.new("playroom", 1, 1)
]

var h2Upstairs = [
    FCNode.new("bedrooms"),
    FCNode.new("bathroom"),
    FCNode.new("toilet"),
    FCNode.new("attics", 1, 0.6)
]

var h2Groundfloor = [
    FCNode.new("kitchen"),
    FCNode.new("living_rooms"),
    FCNode.new("wet_room_&_toilet"),
    FCNode.new("garage"),
    FCNode.new("garden", 1, 0.9),
    FCNode.new("hot_tub_suite", 1, 1)
]

var h2Basement = [
    FCNode.new("cellars", 1, 1),
    FCNode.new("wine_cellar", 1, 1),
    FCNode.new("cinema", 1, 0.75)
]

var h2UpstairsBedrooms = [
    FCNode.new("suite_1"),
    FCNode.new("suite_2"),
    FCNode.new("bedroom_3"),
    FCNode.new("bedroom_4")
]

var h2GroundfloorLivingRooms = [
    FCNode.new("lounge"),
    FCNode.new("dining_room"),
    FCNode.new("conservatory"),
    FCNode.new("playroom")
]

var cleaning = FCNode.new("cleaning")

house1[1].addChildren(h1Bathrooms)
house1[4].addChildren(h1LivingRooms)
houses[0].addChildren(house1)

h2Upstairs[0].addChildren(h2UpstairsBedrooms)
house2[0].addChildren(h2Upstairs)
h2Groundfloor[1].addChildren(h2GroundfloorLivingRooms)
house2[1].addChildren(h2Groundfloor)
house2[2].addChildren(h2Basement)
houses[1].addChildren(house2)

cleaning.addChildren(houses)
var topCoverage = cleaning.coverage
Fmt.print("TOP COVERAGE = $8.6f\n", topCoverage)
System.print("NAME HIERARCHY                 | WEIGHT | COVERAGE |")
cleaning.show(0)

h2Basement[2].coverage = 1 // change Cinema node coverage to 1
var diff = cleaning.coverage - topCoverage
System.print("\nIf the coverage of the Cinema node were increased from 0.75 to 1")
System.write("the top level coverage would increase by ")
Fmt.print("$8.6f to $8.6f", diff, topCoverage + diff)
h2Basement[2].coverage = 0.75  // restore to original value if required
Output:
TOP COVERAGE = 0.409167

NAME HIERARCHY                 | WEIGHT | COVERAGE |
cleaning                       |    1   | 0.409167 |
    house1                     |   40   | 0.331250 |
        bedrooms               |    1   | 0.250000 |
        bathrooms              |    1   | 0.500000 |
            bathroom1          |    1   | 0.500000 |
            bathroom2          |    1   | 0.000000 |
            outside_lavatory   |    1   | 1.000000 |
        attic                  |    1   | 0.750000 |
        kitchen                |    1   | 0.100000 |
        living_rooms           |    1   | 0.250000 |
            lounge             |    1   | 0.000000 |
            dining_room        |    1   | 0.000000 |
            conservatory       |    1   | 0.000000 |
            playroom           |    1   | 1.000000 |
        basement               |    1   | 0.000000 |
        garage                 |    1   | 0.000000 |
        garden                 |    1   | 0.800000 |
    house2                     |   60   | 0.461111 |
        upstairs               |    1   | 0.150000 |
            bedrooms           |    1   | 0.000000 |
                suite_1        |    1   | 0.000000 |
                suite_2        |    1   | 0.000000 |
                bedroom_3      |    1   | 0.000000 |
                bedroom_4      |    1   | 0.000000 |
            bathroom           |    1   | 0.000000 |
            toilet             |    1   | 0.000000 |
            attics             |    1   | 0.600000 |
        groundfloor            |    1   | 0.316667 |
            kitchen            |    1   | 0.000000 |
            living_rooms       |    1   | 0.000000 |
                lounge         |    1   | 0.000000 |
                dining_room    |    1   | 0.000000 |
                conservatory   |    1   | 0.000000 |
                playroom       |    1   | 0.000000 |
            wet_room_&_toilet  |    1   | 0.000000 |
            garage             |    1   | 0.000000 |
            garden             |    1   | 0.900000 |
            hot_tub_suite      |    1   | 1.000000 |
        basement               |    1   | 0.916667 |
            cellars            |    1   | 1.000000 |
            wine_cellar        |    1   | 1.000000 |
            cinema             |    1   | 0.750000 |

If the coverage of the Cinema node were increased from 0.75 to 1
the top level coverage would increase by 0.016667 to 0.425833