eo-phi-normalizer-0.3.1: src/Language/EO/Phi/Report/Data.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-partial-fields #-}
module Language.EO.Phi.Report.Data where
import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics (Generic)
import Language.EO.Phi.Metrics.Data (BindingMetrics (..), Metrics (..), MetricsCount, ProgramMetrics)
import Language.EO.Phi.Metrics.Data qualified as Metrics
import Language.EO.Phi.TH (deriveJSON)
import Text.Printf (printf)
import Prelude hiding (div, id, span)
data ReportItem = ReportItem
{ phi :: FilePath
, phiNormalized :: FilePath
, bindingsPathPhi :: Maybe String
, bindingsPathPhiNormalized :: Maybe String
}
deriving stock (Show, Generic)
$(deriveJSON ''ReportItem)
data MetricsChangeCategory a
= MetricsChange'Good {change :: a}
| MetricsChange'Bad {change :: a}
| MetricsChange'NA
deriving stock (Show, Generic, Eq)
$(deriveJSON ''MetricsChangeCategory)
type MetricsChange = Metrics Percent
newtype Percent = Percent {percent :: Double}
deriving newtype
(FromJSON, ToJSON, Num, Fractional, Floating, Eq, Ord, Real, RealFrac, RealFloat)
roundToStr :: Int -> Double -> String
roundToStr = printf "%0.*f%%"
instance Show Percent where
show :: Percent -> String
show Percent{..} = roundToStr 2 (percent * 100)
type MetricsChangeCategorized = Metrics (MetricsChangeCategory Percent)
data Report'InputConfig = Report'InputConfig
{ js :: Maybe FilePath
, css :: Maybe FilePath
}
deriving stock (Show, Generic)
$(deriveJSON ''Report'InputConfig)
data Report'OutputConfig = Report'OutputConfig
{ html :: Maybe FilePath
, json :: Maybe FilePath
, markdown :: Maybe FilePath
}
deriving stock (Show, Generic)
$(deriveJSON ''Report'OutputConfig)
data ReportConfig = ReportConfig
{ input :: Maybe Report'InputConfig
, output :: Report'OutputConfig
, expectedMetricsChange :: MetricsChange
, expectedImprovedProgramsPercentage :: Percent
, items :: [ReportItem]
}
deriving stock (Show, Generic)
$(deriveJSON ''ReportConfig)
data ReportRow = ReportRow
{ fileInitial :: Maybe FilePath
, fileNormalized :: Maybe FilePath
, bindingsPathInitial :: Maybe Metrics.Path
, bindingsPathNormalized :: Maybe Metrics.Path
, attributeInitial :: Maybe String
, attributeNormalized :: Maybe String
, metricsChange :: MetricsChangeCategorized
, metricsInitial :: Metrics Int
, metricsNormalized :: Metrics Int
}
$(deriveJSON ''ReportRow)
data ProgramReport = ProgramReport
{ programRow :: ReportRow
, bindingsRows :: [ReportRow]
}
$(deriveJSON ''ProgramReport)
data Report = Report
{ totalRow :: ReportRow
, programReports :: [ProgramReport]
}
$(deriveJSON ''Report)
-- >>> calculateMetricsChange Metrics { dataless = 0.1, applications = 0.2, formations = 0.2, dispatches = 0.2 } Metrics { dataless = 100, applications = 0, formations = 100, dispatches = 100 } Metrics { dataless = 90, applications = 0, formations = 93, dispatches = 60 }
-- Metrics {formations = MetricsChange'Bad {change = 7.00%}, dataless = MetricsChange'Good {change = 10.00%}, applications = MetricsChange'NA, dispatches = MetricsChange'Good {change = 40.00%}}
calculateMetricsChange :: MetricsChange -> MetricsCount -> MetricsCount -> MetricsChangeCategorized
calculateMetricsChange expectedMetricsChange countInitial countNormalized =
getMetricsChangeClassified <$> expectedMetricsChange <*> actualMetricsChange
where
isFinite :: (RealFloat a) => a -> Bool
isFinite x = not (isNaN x || isInfinite x)
getMetricsChangeClassified :: Percent -> Percent -> MetricsChangeCategory Percent
getMetricsChangeClassified expected actual
| isFinite expected && isFinite actual =
if actual >= expected
then MetricsChange'Good actual
else MetricsChange'Bad actual
| otherwise = MetricsChange'NA
actualMetricsChange :: MetricsChange
actualMetricsChange = (initial - normalized) / initial
initial = fromIntegral <$> countInitial
normalized = fromIntegral <$> countNormalized
makeProgramReport :: ReportConfig -> ReportItem -> ProgramMetrics -> ProgramMetrics -> ProgramReport
makeProgramReport reportConfig reportItem metricsPhi metricsPhiNormalized =
ProgramReport{..}
where
bindingsRows =
case (metricsPhi.bindingsByPathMetrics, metricsPhiNormalized.bindingsByPathMetrics) of
(Just bindingsMetricsNormalized, Just bindingsMetricsInitial) ->
[ ReportRow
{ fileInitial = Just reportItem.phi
, fileNormalized = Just reportItem.phiNormalized
, bindingsPathInitial = Just bindingsMetricsNormalized.path
, bindingsPathNormalized = Just bindingsMetricsNormalized.path
, attributeInitial = Just attributeInitial
, attributeNormalized = Just attributeNormalized
, metricsChange = calculateMetricsChange reportConfig.expectedMetricsChange metricsInitial metricsNormalized
, metricsInitial = metricsInitial
, metricsNormalized = metricsNormalized
}
| BindingMetrics{name = attributeInitial, metrics = metricsInitial} <- bindingsMetricsInitial.bindingsMetrics
| BindingMetrics{name = attributeNormalized, metrics = metricsNormalized} <- bindingsMetricsNormalized.bindingsMetrics
]
_ -> []
programRow =
ReportRow
{ fileInitial = Just reportItem.phi
, fileNormalized = Just reportItem.phiNormalized
, bindingsPathInitial = Nothing
, bindingsPathNormalized = Nothing
, attributeInitial = Nothing
, attributeNormalized = Nothing
, metricsChange = calculateMetricsChange reportConfig.expectedMetricsChange metricsPhi.programMetrics metricsPhiNormalized.programMetrics
, metricsInitial = metricsPhi.programMetrics
, metricsNormalized = metricsPhiNormalized.programMetrics
}
makeReport :: ReportConfig -> [ProgramReport] -> Report
makeReport reportConfig programReports =
Report{..}
where
programRows = (.programRow) <$> programReports
metricsInitial = foldMap (.metricsInitial) programRows
metricsNormalized = foldMap (.metricsNormalized) programRows
metricsChange = calculateMetricsChange reportConfig.expectedMetricsChange metricsInitial metricsNormalized
totalRow =
ReportRow
{ fileInitial = Nothing
, fileNormalized = Nothing
, bindingsPathInitial = Nothing
, bindingsPathNormalized = Nothing
, attributeInitial = Nothing
, attributeNormalized = Nothing
, ..
}