2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and
31 * documentation are those of the authors and should not be
32 * interpreted as representing official policies, either expressed
33 * or implied, of GRNET S.A.
36 package gr.grnet.aquarium.logic.accounting
38 import gr.grnet.aquarium.util.shortClassNameOf
39 import algorithm.CostPolicyAlgorithmCompiler
41 import gr.grnet.aquarium.logic.events.{WalletEntry, ResourceEvent}
42 import collection.immutable.SortedMap
44 import com.ckkloverdos.maybe.{NoVal, Maybe, Failed, Just}
45 import gr.grnet.aquarium.util.date.MutableDateCalc
46 import gr.grnet.aquarium.util.{ContextualLogger, CryptoUtils, Loggable}
47 import gr.grnet.aquarium.store.PolicyStore
50 * A timeslot together with the algorithm and unit price that apply for this particular timeslot.
54 * @param algorithmDefinition
56 * @param computedCredits The computed credits
58 case class Chargeslot(startMillis: Long,
60 algorithmDefinition: String,
62 computedCredits: Option[Double] = None) {
64 override def toString = "%s(%s, %s, %s, %s, %s)".format(
65 shortClassNameOf(this),
66 new MutableDateCalc(startMillis).toYYYYMMDDHHMMSSSSS,
67 new MutableDateCalc(stopMillis).toYYYYMMDDHHMMSSSSS,
75 * Methods for converting accounting events to wallet entries.
77 * @author Georgios Gousios <gousiosg@gmail.com>
78 * @author Christos KK Loverdos <loverdos@gmail.com>
80 trait Accounting extends DSLUtils with Loggable {
82 * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
84 * @param referenceTimeslot
85 * @param policyTimeslots
86 * @param agreementTimeslots
90 def splitTimeslotByPoliciesAndAgreements(referenceTimeslot: Timeslot,
91 policyTimeslots: List[Timeslot],
92 agreementTimeslots: List[Timeslot],
93 clogM: Maybe[ContextualLogger] = NoVal): List[Timeslot] = {
95 // val clog = ContextualLogger.fromOther(clogM, logger, "splitTimeslotByPoliciesAndAgreements()")
98 // Align policy and agreement validity timeslots to the referenceTimeslot
99 val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
100 val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
102 // clog.debug("referenceTimeslot = %s", referenceTimeslot)
103 // clog.debugSeq("alignedPolicyTimeslots", alignedPolicyTimeslots, 0)
104 // clog.debugSeq("alignedAgreementTimeslots", alignedAgreementTimeslots, 0)
106 val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
107 // clog.debugSeq("result", result, 1)
113 * Given a reference timeslot, we have to break it up to a series of timeslots where a particular
114 * algorithm and price unit is in effect.
118 def resolveEffectiveAlgorithmsAndPriceLists(alignedTimeslot: Timeslot,
119 agreement: DSLAgreement,
120 clogM: Maybe[ContextualLogger] = NoVal): (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
122 val clog = ContextualLogger.fromOther(clogM, logger, "resolveEffectiveAlgorithmsAndPriceLists()")
124 // Note that most of the code is taken from calcChangeChunks()
125 val alg = resolveEffectiveAlgorithmsForTimeslot(alignedTimeslot, agreement)
126 val pri = resolveEffectivePricelistsForTimeslot(alignedTimeslot, agreement)
127 val chargeChunks = splitChargeChunks(alg, pri)
128 val algorithmByTimeslot = chargeChunks._1
129 val pricelistByTimeslot = chargeChunks._2
131 assert(algorithmByTimeslot.size == pricelistByTimeslot.size)
133 (algorithmByTimeslot, pricelistByTimeslot)
137 def computeInitialChargeslots(referenceTimeslot: Timeslot,
138 dslResource: DSLResource,
139 policiesByTimeslot: Map[Timeslot, DSLPolicy],
140 agreementNamesByTimeslot: Map[Timeslot, String],
141 contextualLogger: Maybe[ContextualLogger] = NoVal): Maybe[List[Chargeslot]] = Maybe {
143 val clog = ContextualLogger.fromOther(contextualLogger, logger, "computeInitialChargeslots()")
146 val policyTimeslots = policiesByTimeslot.keySet
147 val agreementTimeslots = agreementNamesByTimeslot.keySet
149 // clog.debugMap("policiesByTimeslot", policiesByTimeslot, 1)
150 // clog.debugMap("agreementNamesByTimeslot", agreementNamesByTimeslot, 1)
152 def getPolicy(ts: Timeslot): DSLPolicy = {
153 policiesByTimeslot.find(_._1.contains(ts)).get._2
155 def getAgreementName(ts: Timeslot): String = {
156 agreementNamesByTimeslot.find(_._1.contains(ts)).get._2
159 // 1. Round ONE: split time according to overlapping policies and agreements.
160 // clog.begin("ROUND 1")
161 val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
162 // clog.debugSeq("alignedTimeslots", alignedTimeslots, 1)
163 // clog.end("ROUND 1")
165 // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
166 // fine-grained timeslots according to applicable algorithms.
167 // Then pack the info into charge slots.
168 // clog.begin("ROUND 2")
169 val allChargeslots = for {
170 alignedTimeslot <- alignedTimeslots
172 // val alignedTimeslotMsg = "alignedTimeslot = %s".format(alignedTimeslot)
173 // clog.begin(alignedTimeslotMsg)
175 val dslPolicy = getPolicy(alignedTimeslot)
176 // clog.debug("dslPolicy = %s", dslPolicy)
177 val agreementName = getAgreementName(alignedTimeslot)
178 // clog.debug("agreementName = %s", agreementName)
179 val agreementOpt = dslPolicy.findAgreement(agreementName)
180 // clog.debug("agreementOpt = %s", agreementOpt)
184 val errMsg = "Unknown agreement %s during %s".format(agreementName, alignedTimeslot)
185 clog.error("%s", errMsg)
186 throw new Exception(errMsg)
188 case Some(agreement) ⇒
189 // TODO: Factor this out, just like we did with:
190 // TODO: val alignedTimeslots = splitTimeslotByPoliciesAndAgreements
191 // Note that most of the code is already taken from calcChangeChunks()
192 val r = resolveEffectiveAlgorithmsAndPriceLists(alignedTimeslot, agreement, Just(clog))
193 val algorithmByTimeslot: Map[Timeslot, DSLAlgorithm] = r._1
194 val pricelistByTimeslot: Map[Timeslot, DSLPriceList] = r._2
196 // Now, the timeslots must be the same
197 val finegrainedTimeslots = algorithmByTimeslot.keySet
199 val chargeslots = for {
200 finegrainedTimeslot <- finegrainedTimeslots
202 // val finegrainedTimeslotMsg = "finegrainedTimeslot = %s".format(finegrainedTimeslot)
203 // clog.begin(finegrainedTimeslotMsg)
205 val dslAlgorithm = algorithmByTimeslot(finegrainedTimeslot) // TODO: is this correct?
206 // clog.debug("dslAlgorithm = %s", dslAlgorithm)
207 // clog.debugMap("dslAlgorithm.algorithms", dslAlgorithm.algorithms, 1)
208 val dslPricelist = pricelistByTimeslot(finegrainedTimeslot) // TODO: is this correct?
209 // clog.debug("dslPricelist = %s", dslPricelist)
210 // clog.debug("dslResource = %s", dslResource)
211 val algorithmDefOpt = dslAlgorithm.algorithms.get(dslResource)
212 // clog.debug("algorithmDefOpt = %s", algorithmDefOpt)
213 val priceUnitOpt = dslPricelist.prices.get(dslResource)
214 // clog.debug("priceUnitOpt = %s", priceUnitOpt)
216 val chargeslot = (algorithmDefOpt, priceUnitOpt) match {
219 "Unknown algorithm and price unit for resource %s during %s".
220 format(dslResource, finegrainedTimeslot))
223 "Unknown algorithm for resource %s during %s".
224 format(dslResource, finegrainedTimeslot))
227 "Unknown price unit for resource %s during %s".
228 format(dslResource, finegrainedTimeslot))
229 case (Some(algorithmDefinition), Some(priceUnit)) ⇒
230 Chargeslot(finegrainedTimeslot.from.getTime, finegrainedTimeslot.to.getTime, algorithmDefinition, priceUnit)
233 // clog.end(finegrainedTimeslotMsg)
237 // clog.end(alignedTimeslotMsg)
241 // clog.end("ROUND 2")
244 val result = allChargeslots.flatten
245 // clog.debugSeq("result", allChargeslots, 1)
251 * Compute the charge slots generated by a particular resource event.
254 def computeFullChargeslots(previousResourceEventM: Maybe[ResourceEvent],
255 currentResourceEvent: ResourceEvent,
257 oldTotalAmount: Double,
258 newTotalAmount: Double,
259 dslResource: DSLResource,
260 defaultResourceMap: DSLResourcesMap,
261 agreementNamesByTimeslot: Map[Timeslot, String],
262 algorithmCompiler: CostPolicyAlgorithmCompiler,
263 policyStore: PolicyStore,
264 contextualLogger: Maybe[ContextualLogger] = NoVal): Maybe[(Timeslot, List[Chargeslot])] = Maybe {
266 val clog = ContextualLogger.fromOther(contextualLogger, logger, "computeFullChargeslots()")
269 val occurredDate = currentResourceEvent.occurredDate
270 val occurredMillis = currentResourceEvent.occurredMillis
271 val costPolicy = dslResource.costPolicy
274 val (referenceTimeslot, relevantPolicies, previousValue) = costPolicy.needsPreviousEventForCreditAndAmountCalculation match {
275 // We need a previous event
277 previousResourceEventM match {
278 // We have a previous event
279 case Just(previousResourceEvent) ⇒
280 // clog.debug("Have previous event")
281 // clog.debug("previousValue = %s", previousResourceEvent.value)
283 val referenceTimeslot = Timeslot(previousResourceEvent.occurredDate, occurredDate)
284 // clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
286 // all policies within the interval from previous to current resource event
287 // clog.debug("Calling policyStore.loadAndSortPoliciesWithin(%s)", referenceTimeslot)
288 val relevantPolicies = policyStore.loadAndSortPoliciesWithin(referenceTimeslot.from.getTime, referenceTimeslot.to.getTime, dsl)
289 // clog.debugMap("==> relevantPolicies", relevantPolicies, 0)
291 (referenceTimeslot, relevantPolicies, previousResourceEvent.value)
293 // We do not have a previous event
296 "Unable to charge. No previous event given for %s".
297 format(currentResourceEvent.toDebugString()))
299 // We could not obtain a previous event
300 case failed @ Failed(e, m) ⇒
302 "Unable to charge. Could not obtain previous event for %s".
303 format(currentResourceEvent.toDebugString()), e)
306 // We do not need a previous event
308 // ... so we cannot compute timedelta from a previous event, there is just one chargeslot
309 // referring to (almost) an instant in time
310 // clog.debug("DO NOT have previous event")
311 val previousValue = costPolicy.getResourceInstanceUndefinedAmount
312 // clog.debug("previousValue = costPolicy.getResourceInstanceUndefinedAmount = %s", previousValue)
314 val referenceTimeslot = Timeslot(new MutableDateCalc(occurredDate).goPreviousMilli.toDate, occurredDate)
315 // clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
317 // clog.debug("Calling policyStore.loadValidPolicyEntryAt(%s)", new MutableDateCalc(occurredMillis))
318 val relevantPolicyM = policyStore.loadValidPolicyAt(occurredMillis, dsl)
319 // clog.debug(" ==> relevantPolicyM = %s", relevantPolicyM)
321 val relevantPolicies = relevantPolicyM match {
322 case Just(relevantPolicy) ⇒
323 Map(referenceTimeslot -> relevantPolicy)
325 throw new Exception("No relevant policy found for %s".format(referenceTimeslot))
326 case failed @ Failed(e, _) ⇒
327 throw new Exception("No relevant policy found for %s".format(referenceTimeslot), e)
331 (referenceTimeslot, relevantPolicies, previousValue)
334 val initialChargeslotsM = computeInitialChargeslots(
338 agreementNamesByTimeslot,
342 val fullChargeslotsM = initialChargeslotsM.map { chargeslots ⇒
344 case chargeslot @ Chargeslot(startMillis, stopMillis, algorithmDefinition, unitPrice, _) ⇒
345 val execAlgorithmM = algorithmCompiler.compile(algorithmDefinition)
346 execAlgorithmM match {
348 throw new Exception("Could not compile algorithm %s".format(algorithmDefinition))
350 case failed @ Failed(e, m) ⇒
351 throw new Exception(m, e)
353 case Just(execAlgorithm) ⇒
354 val valueMap = costPolicy.makeValueMap(
358 stopMillis - startMillis,
360 currentResourceEvent.value,
364 // clog.debug("execAlgorithm = %s", execAlgorithm)
365 clog.debugMap("valueMap", valueMap, 1)
368 val creditsM = execAlgorithm.apply(valueMap)
373 "Could not compute credits for resource %s during %s".
374 format(dslResource.name, Timeslot(new Date(startMillis), new Date(stopMillis))))
376 case failed @ Failed(e, m) ⇒
377 throw new Exception(m, e)
380 chargeslot.copy(computedCredits = Some(credits))
386 val result = fullChargeslotsM match {
387 case Just(fullChargeslots) ⇒
388 referenceTimeslot -> fullChargeslots
391 case failed @ Failed(e, m) ⇒
392 throw new Exception(m, e)
401 * Create a list of wallet entries by charging for a resource event.
403 * @param currentResourceEvent The resource event to create charges for
404 * @param agreements The user's agreement names, indexed by their
405 * applicability timeslot
406 * @param previousAmount The current state of the resource
407 * @param previousOccurred The last time the resource state was updated
409 def chargeEvent(currentResourceEvent: ResourceEvent,
410 agreements: SortedMap[Timeslot, String],
411 previousAmount: Double,
412 previousOccurred: Date,
413 related: List[WalletEntry]): Maybe[List[WalletEntry]] = {
415 assert(previousOccurred.getTime <= currentResourceEvent.occurredMillis)
416 val occuredDate = new Date(currentResourceEvent.occurredMillis)
418 /* The following makes sure that agreements exist between the start
419 * and end days of the processed event. As agreement updates are
420 * guaranteed not to leave gaps, this means that the event can be
421 * processed correctly, as at least one agreement will be valid
422 * throughout the event's life.
425 agreements.keysIterator.exists {
426 p => p.includes(occuredDate)
427 } && agreements.keysIterator.exists {
428 p => p.includes(previousOccurred)
432 val t = Timeslot(previousOccurred, occuredDate)
434 // Align policy and agreement validity timeslots to the event's boundaries
435 val policyTimeslots = t.align(
436 Policy.policies(previousOccurred, occuredDate).keysIterator.toList)
437 val agreementTimeslots = t.align(agreements.keysIterator.toList)
440 * Get a set of timeslot slices covering the different durations of
441 * agreements and policies.
443 val aligned = alignTimeslots(policyTimeslots, agreementTimeslots)
445 val walletEntries = aligned.map {
447 // Retrieve agreement from the policy valid at time of event
448 val agreementName = agreements.find(y => y._1.contains(x)) match {
450 case None => return Failed(new AccountingException(("Cannot find" +
451 " user agreement for period %s").format(x)))
454 // Do the wallet entry calculation
455 val entries = chargeEvent(
456 currentResourceEvent,
457 Policy.policy(x.from).findAgreement(agreementName._2).getOrElse(
458 return Failed(new AccountingException("Cannot get agreement for %s".format()))
466 case Failed(f, e) => return Failed(f,e)
476 * Creates a list of wallet entries by applying the agreement provisions on
477 * the resource state.
479 * @param event The resource event to create charges for
480 * @param agr The agreement implementation to use
481 * @param previousAmount The current state of the resource
482 * @param previousOccurred The timestamp of the previous event
483 * @param related Related wallet entries (TODO: should remove)
484 * @param chargeFor The duration for which the charge should be done.
485 * Should fall between the previous and current
486 * resource event boundaries
487 * @return A list of wallet entries, one for each
489 def chargeEvent(event: ResourceEvent,
491 previousAmount: Double,
492 previousOccurred: Date,
493 related: List[WalletEntry],
494 chargeFor: Option[Timeslot]): Maybe[List[WalletEntry]] = {
496 // If chargeFor is not null, make sure it falls within
497 // event time boundaries
498 chargeFor.map{x => assert(true,
499 Timeslot(previousOccurred, new Date(event.occurredMillis)))}
501 if (!event.validate())
502 return Failed(new AccountingException("Event not valid"))
504 val policy = Policy.policy
505 val dslResource = policy.findResource(event.resource) match {
507 case None => return Failed(
508 new AccountingException("No resource [%s]".format(event.resource)))
511 /* This is a safeguard against the special case where the last
512 * resource state update, as marked by the lastUpdate parameter
513 * is equal to the time of the event occurrence. This means that
514 * this is the first time the resource state has been recorded.
515 * Charging in this case only makes sense for discrete resources.
517 if (previousOccurred.getTime == event.occurredMillis) {
518 dslResource.costPolicy match {
519 case DiscreteCostPolicy => //Ok
520 case _ => return Some(List())
524 val creditCalculationValueM = dslResource.costPolicy.getValueForCreditCalculation(Just(previousAmount), event.value)
525 val amount = creditCalculationValueM match {
526 case failed @ Failed(_, _) ⇒
534 // We don't do strict checking for all cases for OnOffPolicies as
535 // above, since this point won't be reached in case of error.
536 val isFinal = dslResource.costPolicy match {
537 case OnOffCostPolicy =>
538 OnOffPolicyResourceState(previousAmount) match {
539 case OnResourceState => false
540 case OffResourceState => true
546 * Get the timeslot for which this event will be charged. In case we
547 * have a discrete resource, we do not really care for the time duration
548 * of an event. To process all events in a uniform way, we create an
549 * artificial timeslot lasting the minimum amount of time. In all other
550 * cases, we first check whether a desired charge period passed as
553 val timeslot = dslResource.costPolicy match {
554 case DiscreteCostPolicy => Timeslot(new Date(event.occurredMillis - 1),
555 new Date(event.occurredMillis))
556 case _ => chargeFor match {
558 case None => Timeslot(previousOccurred, new Date(event.occurredMillis))
563 * The following splits the chargable timeslot into smaller timeslots to
564 * comply with different applicability periods for algorithms and
565 * pricelists defined by the provided agreement.
567 val chargeChunks = calcChangeChunks(agr, amount, dslResource, timeslot)
569 val timeReceived = System.currentTimeMillis
571 val rel = event.id :: related.map{x => x.sourceEventIDs}.flatten
573 val entries = chargeChunks.map { c=>
575 id = CryptoUtils.sha1(c.id),
576 occurredMillis = event.occurredMillis,
577 receivedMillis = timeReceived,
578 sourceEventIDs = rel,
581 userId = event.userId,
582 resource = event.resource,
583 instanceId = event.instanceId,
593 def calcChangeChunks(agr: DSLAgreement, volume: Double,
594 res: DSLResource, t: Timeslot): List[ChargeChunk] = {
596 val alg = resolveEffectiveAlgorithmsForTimeslot(t, agr)
597 val pri = resolveEffectivePricelistsForTimeslot(t, agr)
598 val chunks = splitChargeChunks(alg, pri)
599 val algChunked = chunks._1
600 val priChunked = chunks._2
602 assert(algChunked.size == priChunked.size)
604 res.costPolicy match {
605 case DiscreteCostPolicy => calcChargeChunksDiscrete(algChunked, priChunked, volume, res)
606 case _ => calcChargeChunksContinuous(algChunked, priChunked, volume, res)
611 * Get a list of charge chunks for discrete resources.
614 def calcChargeChunksDiscrete(algChunked: Map[Timeslot, DSLAlgorithm],
615 priChunked: Map[Timeslot, DSLPriceList],
616 volume: Double, res: DSLResource): List[ChargeChunk] = {
617 // In case of descrete resources, we only a expect a
618 assert(algChunked.size == 1)
619 assert(priChunked.size == 1)
620 assert(algChunked.keySet.head.compare(priChunked.keySet.head) == 0)
622 List(ChargeChunk(volume,
623 algChunked.valuesIterator.next.algorithms.getOrElse(res, ""),
624 priChunked.valuesIterator.next.prices.getOrElse(res, 0),
625 algChunked.keySet.head, res))
629 * Get a list of charge chunks for continuous resources.
632 def calcChargeChunksContinuous(algChunked: Map[Timeslot, DSLAlgorithm],
633 priChunked: Map[Timeslot, DSLPriceList],
634 volume: Double, res: DSLResource): List[ChargeChunk] = {
635 algChunked.keysIterator.map {
638 algChunked.get(x).get.algorithms.getOrElse(res, ""),
639 priChunked.get(x).get.prices.getOrElse(res, 0), x, res)
644 * Align charge timeslots between algorithms and pricelists. As algorithm
645 * and pricelists can have different effectivity periods, this method
646 * examines them and splits them as necessary.
648 private[logic] def splitChargeChunks(alg: SortedMap[Timeslot, DSLAlgorithm],
649 price: SortedMap[Timeslot, DSLPriceList]) :
650 (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
652 val zipped = alg.keySet.zip(price.keySet)
654 zipped.find(p => !p._1.equals(p._2)) match {
655 case None => (alg, price)
657 val algTimeslot = x._1
658 val priTimeslot = x._2
660 assert(algTimeslot.from == priTimeslot.from)
662 if (algTimeslot.endsAfter(priTimeslot)) {
663 val slices = algTimeslot.slice(priTimeslot.to)
664 val algo = alg.get(algTimeslot).get
665 val newalg = alg - algTimeslot ++ Map(slices.apply(0) -> algo) ++ Map(slices.apply(1) -> algo)
666 splitChargeChunks(newalg, price)
669 val slices = priTimeslot.slice(priTimeslot.to)
670 val pl = price.get(priTimeslot).get
671 val newPrice = price - priTimeslot ++ Map(slices.apply(0) -> pl) ++ Map(slices.apply(1) -> pl)
672 splitChargeChunks(alg, newPrice)
678 * Given two lists of timeslots, produce a list which contains the
679 * set of timeslot slices, as those are defined by
682 * For example, given the timeslots a and b below, split them as shown.
684 * a = |****************|
691 * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
693 private[logic] def alignTimeslots(a: List[Timeslot],
694 b: List[Timeslot]): List[Timeslot] = {
696 def safeTail(foo: List[Timeslot]) = foo match {
698 case x :: Nil => List()
699 case x :: rest => rest
702 if (a.isEmpty) return b
703 if (b.isEmpty) return a
705 assert (a.head.from == b.head.from)
707 if (a.head.endsAfter(b.head)) {
708 val slice = a.head.slice(b.head.to)
709 slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
710 } else if (b.head.endsAfter(a.head)) {
711 val slice = b.head.slice(a.head.to)
712 slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
714 a.head :: alignTimeslots(safeTail(a), safeTail(b))
720 * Encapsulates a computation for a specific timeslot of
723 case class ChargeChunk(value: Double, algorithm: String,
724 price: Double, when: Timeslot,
725 resource: DSLResource) {
727 assert(!algorithm.isEmpty)
728 assert(resource != null)
731 //TODO: Apply the algorithm, when we start parsing it
732 resource.costPolicy match {
733 case DiscreteCostPolicy =>
736 value * price * when.hours
739 def reason(): String =
740 resource.costPolicy match {
741 case DiscreteCostPolicy =>
742 "%f %s at %s @ %f/%s".format(value, resource.unit, when.from, price,
744 case ContinuousCostPolicy =>
745 "%f %s of %s from %s to %s @ %f/%s".format(value, resource.unit,
746 resource.name, when.from, when.to, price, resource.unit)
747 case OnOffCostPolicy =>
748 "%f %s of %s from %s to %s @ %f/%s".format(when.hours, resource.unit,
749 resource.name, when.from, when.to, price, resource.unit)
753 CryptoUtils.sha1("%f%s%f%s%s%d".format(value, algorithm, price, when.toString,
754 resource.name, System.currentTimeMillis()))
757 /** An exception raised when something goes wrong with accounting */
758 class AccountingException(msg: String) extends Exception(msg)