module VisitCalculation (calculateVisits) where import qualified Data.ByteString.Lazy.Char8 as BLC import Data.Foldable (foldl') import Data.List.Split (splitOn) import qualified Data.Map as Map import Data.Map (Map) import Text.JSON type ParsedEvent = (VisitId, RequestTime, Event) type VisitData = (VisitId, (RequestTime, [Event])) -- events sorted in reverse order type VisitId = String type RequestTime = String type Event = String calculateVisits :: [BLC.ByteString] -> [BLC.ByteString] calculateVisits = map BLC.pack . map formatVisit . partitionByVisitorId . parseEvents . map BLC.unpack -- TODO probably highly imperformant parseEvents :: [String] -> [ParsedEvent] parseEvents = foldl' (\p -> maybe p (:p) . parseEvent) [] where parseEvent :: String -> Maybe ParsedEvent parseEvent e = let first5 = take 5 (splitOn "|" e) in if length first5 < 5 then Nothing else Just (first5 !! 4, first5 !! 0, e) partitionByVisitorId :: [ParsedEvent] -> [VisitData] partitionByVisitorId = Map.assocs . foldr insertEvent Map.empty where insertEvent :: ParsedEvent -> Map VisitId (RequestTime, [Event]) -> Map VisitId (RequestTime, [Event]) insertEvent (k, t, e) = Map.insertWith mergeData k (t, [e]) where mergeData :: (RequestTime, [Event]) -> (RequestTime, [Event]) -> (RequestTime, [Event]) mergeData (newT, newEs) (oldT, oldEs) = (min newT oldT, reverse newEs ++ oldEs) formatVisit :: VisitData -> String formatVisit (i, (t, es)) = encode $ toJSObject [visitId, events] where events, visitId :: (String, JSValue) events = ("events", JSArray $ map (JSString . toJSString) es) visitId = ("visitId", JSObject $ toJSObject [visitorId, lastRequestTime]) where visitorId, lastRequestTime :: (String, JSValue) visitorId = ("visitorId", JSObject $ toJSObject [("value", JSString $ toJSString i)]) lastRequestTime = ("lastRequestTime", JSString $ toJSString t)