private void OnCLEMHerdSummary(object sender, EventArgs e) { if (PaymentCalculationStyle == PayHiredLabourCalculationStyle.ByLabourUsedInTimeStep) { int currentmonth = clock.Today.Month; double total = 0; foreach (LabourType item in labour.Items.Where(a => a.Hired)) { // get days needed double daysUsed = item.LabourAvailability.GetAvailability(currentmonth - 1) - item.AvailableDays; // calculate rate and amount needed double rate = item.PayRate(); total += (daysUsed * rate); } // take hire cost bankAccount.Remove(new ResourceRequest() { Resource = bankAccount, ResourceType = typeof(Finance), AllowTransmutation = false, Required = total, ResourceTypeName = this.AccountName, ActivityModel = this, Category = TransactionCategory }); } }
///<inheritdoc/> public bool DoTransmute(ResourceRequest request, double shortfallPacketsNeeded, double requiredByActivities, ResourcesHolder holder, bool queryOnly) { switch (TransmuteStyle) { case TransmuteStyle.Direct: request.Required = shortfallPacketsNeeded * AmountPerPacket; break; case TransmuteStyle.UsePricing: request.Required = (shortfallPacketsNeeded * shortfallPricing.CurrentPrice) / transmutePricing.CurrentPrice; // check that sell whole packets are honoured if (transmutePricing.UseWholePackets) { double pricingWholePacketAdjusted = Math.Ceiling(request.Required / transmutePricing.PacketSize) * transmutePricing.PacketSize; request.Required = pricingWholePacketAdjusted; } break; default: break; } if (queryOnly) { return(request.Required + requiredByActivities <= TransmuteResourceType.Amount); } else { TransmuteResourceType.Remove(request); if (TransmuteStyle == TransmuteStyle.UsePricing && financeType != null) { // add finance transaction to sell transmute financeType.Add(request.Required * transmutePricing.CurrentPrice, request.ActivityModel, TransmuteResourceTypeName, request.Category); // add finance transaction to buy shortfall ResourceRequest financeRequest = new ResourceRequest() { Resource = financeType, Required = shortfallPacketsNeeded * shortfallPacketSize * shortfallPricing.CurrentPrice, RelatesToResource = request.ResourceTypeName, ResourceType = typeof(Finance), ActivityModel = request.ActivityModel, Category = request.Category, }; financeType.Remove(financeRequest); } } return(true); }
private void OnEndOfMonth(object sender, EventArgs e) { FinanceType bankAccount = Resources.FinanceResource().GetFirst() as FinanceType; // make interest payments on bank accounts foreach (FinanceType accnt in Resources.FinanceResource().Children.Where(a => a.GetType() == typeof(FinanceType))) { if (accnt.Balance > 0) { bankAccount.Add(accnt.Balance * accnt.InterestRatePaid / 1200, this.Name, "InterestPaid"); } else { bankAccount.Remove(Math.Abs(accnt.Balance) * accnt.InterestRateCharged / 1200, this.Name, "InterestCharged"); } } }
private void OnWFAnimalBuy(object sender, EventArgs e) { RuminantHerd ruminantHerd = Resources.RuminantHerd(); Finance Accounts = Resources.FinanceResource() as Finance; FinanceType bankAccount = Accounts.GetFirst() as FinanceType; var newRequests = ruminantHerd.PurchaseIndividuals.Where(a => a.BreedParams.Breed == BreedName).ToList(); foreach (var newgroup in newRequests.GroupBy(a => a.SaleFlag)) { double fundsAvailable = 100000000; if (bankAccount != null) { fundsAvailable = bankAccount.FundsAvailable; } double cost = 0; foreach (var newind in newgroup) { double value = 0; if (newgroup.Key == Common.HerdChangeReason.SirePurchase) { value = BreedingSirePrice; } else { RuminantValue getvalue = PriceList.Where(a => a.Age < newind.Age).OrderBy(a => a.Age).LastOrDefault(); value = getvalue.PurchaseValue * ((getvalue.Style == Common.PricingStyleType.perKg) ? newind.Weight : 1.0); } if (cost + value <= fundsAvailable) { ruminantHerd.AddRuminant(newind); cost += value; } else { break; } } if (bankAccount != null) { bankAccount.Remove(cost, this.Name, newgroup.Key.ToString()); } } }
/// <summary> /// Method used to perform activity if it can occur as soon as resources are available. /// </summary> public override void DoActivity() { Status = ActivityStatus.NotNeeded; // take local equivalent of market from resource double provided = 0; if ((resourceToBuy as CLEMResourceTypeBase).MarketStoreExists) { // find resource entry in market if present and reduce ResourceRequest rr = ResourceRequestList.Where(a => a.Resource == (resourceToBuy as CLEMResourceTypeBase).EquivalentMarketStore).FirstOrDefault(); provided = rr.Provided / this.FarmMultiplier; } else { provided = unitsCanAfford * price.PacketSize; } if (provided > 0) { resourceToBuy.Add(provided, this, "", "Purchase"); Status = ActivityStatus.Success; } // make financial transactions if (bankAccount != null) { ResourceRequest payment = new ResourceRequest() { AllowTransmutation = false, MarketTransactionMultiplier = this.FarmMultiplier, Required = provided / price.PacketSize * price.PricePerPacket, ResourceType = typeof(Finance), ResourceTypeName = bankAccount.Name, Category = "Purchase", RelatesToResource = (resourceToBuy as CLEMModel).NameWithParent, ActivityModel = this }; bankAccount.Remove(payment); } }
/// <summary> /// Method to determine resources required for this activity in the current month /// </summary> /// <returns>List of required resource requests</returns> public override List <ResourceRequest> GetResourcesNeededForActivity() { if (people is null | food is null) { return(null); } List <LabourType> peopleList = people.Items.Where(a => IncludeHiredLabour || a.Hired == false).ToList(); peopleList.Select(a => a.FeedToTargetIntake == 0); // determine AEs to be fed double aE = peopleList.Sum(a => a.AdultEquivalent); if (aE <= 0) { return(null); } int daysInMonth = DateTime.DaysInMonth(Clock.Today.Year, Clock.Today.Month); // determine feed limits (max kg per AE per day * AEs * days) double intakeLimit = DailyIntakeLimit * aE * daysInMonth; // remove previous consumption double otherIntake = this.DailyIntakeOtherSources * aE * daysInMonth; otherIntake += peopleList.Sum(a => a.GetAmountConsumed()); List <LabourActivityFeedTarget> labourActivityFeedTargets = this.FindAllChildren <LabourActivityFeedTarget>().Cast <LabourActivityFeedTarget>().ToList(); // determine targets foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { // calculate target target.Target = target.TargetValue * aE * daysInMonth; // set initial level based on off store inputs target.CurrentAchieved = target.OtherSourcesValue * aE * daysInMonth; // calculate current level from previous intake this month (LabourActivityFeed) target.CurrentAchieved += people.GetDietaryValue(target.Metric, IncludeHiredLabour, true) * aE * daysInMonth; // add sources outside of this activity to peoples' diets if (target.OtherSourcesValue > 0) { foreach (var person in peopleList) { LabourDietComponent outsideEat = new LabourDietComponent(); outsideEat.AddOtherSource(target.Metric, target.OtherSourcesValue * person.AdultEquivalent * daysInMonth); person.AddIntake(outsideEat); } } } // get max months before spoiling of all food stored (will be zero for non perishable food) int maxFoodAge = food.FindAllChildren <HumanFoodStoreType>().Cast <HumanFoodStoreType>().Max(a => a.Pools.Select(b => a.UseByAge - b.Age).DefaultIfEmpty(0).Max()); // create list of all food parcels List <HumanFoodParcel> foodParcels = new List <HumanFoodParcel>(); foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>().Cast <HumanFoodStoreType>().ToList()) { foreach (HumanFoodStorePool pool in foodStore.Pools) { foodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1: foodStore.UseByAge - pool.Age) }); } } foodParcels = foodParcels.OrderBy(a => a.Expires).ToList(); // if a market exists add the available market produce to the list below that ordered above. // order market food by price ascending // this will include market available food in the decisions. // will need to purchase this food before taking it if cost associated. // We can check if the parent of the human food store used is a market and charge accordingly. // for each market List <HumanFoodParcel> marketFoodParcels = new List <HumanFoodParcel>(); ResourcesHolder resources = Resources.FoundMarket.Resources; if (resources != null) { HumanFoodStore food = resources.HumanFoodStore(); if (food != null) { foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>()) { foreach (HumanFoodStorePool pool in foodStore.Pools) { marketFoodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1 : foodStore.UseByAge - pool.Age) }); } } } } foodParcels.AddRange(marketFoodParcels.OrderBy(a => a.FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase).PricePerPacket)); double fundsAvailable = double.PositiveInfinity; if (bankAccount != null) { fundsAvailable = bankAccount.FundsAvailable; } int parcelIndex = 0; double intake = otherIntake; // start eating food from list from that about to expire first while (parcelIndex < foodParcels.Count) { foodParcels[parcelIndex].Proportion = 0; if (intake < intakeLimit & (labourActivityFeedTargets.Where(a => !a.TargetMet).Count() > 0 | foodParcels[parcelIndex].Expires == 0)) { // still able to eat and target not met or food about to expire this timestep // reduce by amout that can be eaten double propCanBeEaten = Math.Min(1, (intakeLimit - intake) / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); // reduce to target limits double propToTarget = 1; if (foodParcels[parcelIndex].Expires != 0) { // if the food is not going to spoil // then adjust what can be eaten up to target otherwise allow over target consumption to avoid waste LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => !a.TargetMet).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target double metricneeded = Math.Max(0, targetUnfilled.Target - targetUnfilled.CurrentAchieved); double amountneeded = metricneeded / foodParcels[parcelIndex].FoodStore.ConversionFactor(targetUnfilled.Metric); propToTarget = Math.Min(1, amountneeded / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); } } foodParcels[parcelIndex].Proportion = Math.Min(propCanBeEaten, propToTarget); // work out if there will be a cost limitation, only if a price structure exists for the resource double propToPrice = 1; if (foodParcels[parcelIndex].FoodStore.PricingExists(PurchaseOrSalePricingStyleType.Purchase)) { ResourcePricing price = foodParcels[parcelIndex].FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase); double cost = (foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion) / price.PacketSize * price.PricePerPacket; if (cost > 0) { propToPrice = Math.Min(1, fundsAvailable / cost); // remove cost from running check tally fundsAvailable = Math.Max(0, fundsAvailable - (cost * propToPrice)); // real finance transactions will happen in the do activity as stuff is allocated // there should not be shortfall as all the checks and reductions have happened here } } foodParcels[parcelIndex].Proportion *= propToPrice; // update intake double newIntake = (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion); intake += newIntake; // update metrics foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { target.CurrentAchieved += newIntake * foodParcels[parcelIndex].FoodStore.ConversionFactor(target.Metric); } } else if (intake >= intakeLimit && labourActivityFeedTargets.Where(a => !a.TargetMet).Count() > 1) { // full but could still reach target with some substitution // but can substitute to remove a previous target // does the current parcel have better target values than any previous non age 0 pool of a different food type } else { break; } parcelIndex++; } // fill resource requests List <ResourceRequest> requests = new List <ResourceRequest>(); foreach (var item in foodParcels.GroupBy(a => a.FoodStore)) { double amount = item.Sum(a => a.Pool.Amount * a.Proportion); if (amount > 0) { double financeLimit = 1; // if obtained from the market make financial transaction before taking ResourcePricing price = item.Key.Price(PurchaseOrSalePricingStyleType.Sale); if (bankAccount != null && item.Key.Parent.Parent.Parent == Market && price.PricePerPacket > 0) { // if shortfall reduce purchase ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amount / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = "Food purchase", MarketTransactionMultiplier = 1 }; bankAccount.Remove(marketRequest); } requests.Add(new ResourceRequest() { Resource = item.Key, ResourceType = typeof(HumanFoodStore), AllowTransmutation = false, Required = amount * financeLimit, ResourceTypeName = item.Key.Name, ActivityModel = this, Category = "Consumption" }); } } // if still hungry and funds available, try buy food in excess of what stores (private or market) offered using transmutation if present. // This will force the market or private sources to purchase more food to meet demand if transmutation available. // if no market is present it will look to transmutating from its own stores if possible. // this means that other than a purchase from market (above) this activity doesn't need to worry about financial tranactions. if (intake < intakeLimit && (labourActivityFeedTargets.Where(a => !a.TargetMet).Count() > 0) && fundsAvailable > 0) { ResourcesHolder resourcesHolder = Resources; // if market is present point to market to find the resource if (Market != null) { resourcesHolder = Market.FindChild <ResourcesHolder>(); } // don't worry about money anymore. The over request will be handled by the transmutation. // move through specified purchase list foreach (LabourActivityFeedTargetPurchase purchase in this.FindAllChildren <LabourActivityFeedTargetPurchase>().Cast <LabourActivityFeedTargetPurchase>().ToList()) { HumanFoodStoreType foodtype = resourcesHolder.GetResourceItem(this, purchase.FoodStoreName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore) as HumanFoodStoreType; if (foodtype != null && (foodtype.TransmutationDefined & intake < intakeLimit)) { LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => !a.TargetMet).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target double metricneeded = Math.Max(0, (targetUnfilled.Target - targetUnfilled.CurrentAchieved)); double amountneeded = metricneeded / foodtype.ConversionFactor(targetUnfilled.Metric); if (intake + amountneeded > intakeLimit) { amountneeded = intakeLimit - intake; } double amountfood = amountneeded / foodtype.EdibleProportion; // update intake intake += amountfood; // find in requests or create a new one ResourceRequest foodRequestFound = requests.Find(a => a.Resource == foodtype) as ResourceRequest; if (foodRequestFound is null) { requests.Add(new ResourceRequest() { Resource = foodtype, ResourceType = typeof(HumanFoodStore), AllowTransmutation = true, Required = amountfood, ResourceTypeName = purchase.FoodStoreName.Split('.')[1], ActivityModel = this, Category = "Consumption" }); } else { foodRequestFound.Required += amountneeded; foodRequestFound.AllowTransmutation = true; } } } } // NOTE: proportions of purchased food are not modified if the sum does not add up to 1 or some of the food types are not available. } return(requests); }
/// <inheritdoc/> public override List <ResourceRequest> GetResourcesNeededForActivity() { if (people is null | food is null) { return(null); } List <LabourType> peopleList = people.Items.Where(a => IncludeHiredLabour || a.Hired == false).ToList(); peopleList.Select(a => { a.FeedToTargetIntake = 0; return(a); }).ToList(); // determine AEs to be fed double aE = peopleList.Sum(a => a.TotalAdultEquivalents); if (aE <= 0) { return(null); } int daysInMonth = DateTime.DaysInMonth(clock.Today.Year, clock.Today.Month); // determine feed limits (max kg per AE per day * AEs * days) double intakeLimit = DailyIntakeLimit * aE * daysInMonth; // remove previous consumption double otherIntake = this.DailyIntakeOtherSources * aE * daysInMonth; otherIntake += peopleList.Sum(a => a.GetAmountConsumed()); List <LabourActivityFeedTarget> labourActivityFeedTargets = this.FindAllChildren <LabourActivityFeedTarget>().ToList(); // determine targets foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { // calculate target target.Target = target.TargetValue * aE * daysInMonth; // calculate target maximum target.TargetMaximum = target.TargetMaximumValue * aE * daysInMonth; // set initial level based on off store inputs target.CurrentAchieved = target.OtherSourcesValue * aE * daysInMonth; // calculate current level from previous intake this month (LabourActivityFeed) target.CurrentAchieved += people.GetDietaryValue(target.Metric, IncludeHiredLabour, false); // * aE; // * daysInMonth; // add sources outside of this activity to peoples' diets if (target.OtherSourcesValue > 0) { foreach (var person in peopleList) { LabourDietComponent outsideEat = new LabourDietComponent(); // TODO: might need to add consumed here outsideEat.AmountConsumed = this.DailyIntakeOtherSources * person.TotalAdultEquivalents * daysInMonth; outsideEat.AddOtherSource(target.Metric, target.OtherSourcesValue * person.TotalAdultEquivalents * daysInMonth); // track this consumption by people here. person.AddIntake(outsideEat); person.FeedToTargetIntake += outsideEat.AmountConsumed; } } } // get max months before spoiling of all food stored (will be zero for non perishable food) int maxFoodAge = food.FindAllChildren <HumanFoodStoreType>().Max(a => a.Pools.Select(b => a.UseByAge - b.Age).DefaultIfEmpty(0).Max()); // create list of all food parcels List <HumanFoodParcel> foodParcels = new List <HumanFoodParcel>(); foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>().ToList()) { foreach (HumanFoodStorePool pool in foodStore.Pools.Where(a => a.Amount > 0)) { foodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1: foodStore.UseByAge - pool.Age) }); } } foodParcels = foodParcels.OrderBy(a => a.Expires).ToList(); // if a market exists add the available market produce to the list below that ordered above. // order market food by price ascending // this will include market available food in the decisions. // will need to purchase this food before taking it if cost associated. // We can check if the parent of the human food store used is a market and charge accordingly. // for each market List <HumanFoodParcel> marketFoodParcels = new List <HumanFoodParcel>(); ResourcesHolder resources = Market?.Resources; if (resources != null) { HumanFoodStore food = resources.FindResourceGroup <HumanFoodStore>(); if (food != null) { foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>()) { foreach (HumanFoodStorePool pool in foodStore.Pools.Where(a => a.Amount > 0)) { marketFoodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1 : foodStore.UseByAge - pool.Age) }); } } } } foodParcels.AddRange(marketFoodParcels.OrderBy(a => a.FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase).PricePerPacket)); double fundsAvailable = double.PositiveInfinity; if (bankAccount != null) { fundsAvailable = bankAccount.FundsAvailable; } int parcelIndex = 0; double metricneeded = 0; double intake = otherIntake; // start eating food from list from that about to expire first // food from household can be eaten up to target maximum // food from market can only be eaten up to target while (parcelIndex < foodParcels.Count) { foodParcels[parcelIndex].Proportion = 0; var isHousehold = foodParcels[parcelIndex].FoodStore.CLEMParentName == this.CLEMParentName; if (intake < intakeLimit & (labourActivityFeedTargets.Where(a => ((isHousehold)? !a.TargetMaximumAchieved: !a.TargetAchieved)).Count() > 0 | foodParcels[parcelIndex].Expires == 0)) { // still able to eat and target not met or food about to expire this timestep // reduce by amout that can be eaten double propCanBeEaten = Math.Min(1, (intakeLimit - intake) / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); // reduce to target limits double propToTarget = 1; if (foodParcels[parcelIndex].Expires != 0) { // if the food is not going to spoil // then adjust what can be eaten up to target otherwise allow over target consumption to avoid waste LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => ((isHousehold) ? !a.TargetMaximumAchieved : !a.TargetAchieved)).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target metricneeded = Math.Max(0, (isHousehold ? targetUnfilled.TargetMaximum : targetUnfilled.Target) - targetUnfilled.CurrentAchieved); double amountneeded = metricneeded / foodParcels[parcelIndex].FoodStore.ConversionFactor(targetUnfilled.Metric); propToTarget = Math.Min(1, amountneeded / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); } } foodParcels[parcelIndex].Proportion = Math.Min(propCanBeEaten, propToTarget); // work out if there will be a cost limitation, only if a price structure exists for the resource // no charge for household consumption double propToPrice = 1; if (!isHousehold && foodParcels[parcelIndex].FoodStore.PricingExists(PurchaseOrSalePricingStyleType.Purchase)) { ResourcePricing price = foodParcels[parcelIndex].FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase); double cost = (foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion) / price.PacketSize * price.PricePerPacket; // TODO: sell cattle based on selling groups till run out of cattle or meet shortfall // adjust fundsAvailable with new money // if cost > 0 and cost > funds available if (cost > 0) { propToPrice = Math.Min(1, fundsAvailable / cost); // remove cost from running check tally fundsAvailable = Math.Max(0, fundsAvailable - (cost * propToPrice)); // real finance transactions will happen in the do activity as stuff is allocated // there should not be shortfall as all the checks and reductions have happened here } } foodParcels[parcelIndex].Proportion *= propToPrice; // update intake double newIntake = (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion); intake += newIntake; // update metrics foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { target.CurrentAchieved += newIntake * foodParcels[parcelIndex].FoodStore.ConversionFactor(target.Metric); } } else if (intake >= intakeLimit && labourActivityFeedTargets.Where(a => ((isHousehold) ? !a.TargetMaximumAchieved : !a.TargetAchieved)).Count() > 1) { // full but could still reach target with some substitution // but can substitute to remove a previous target // does the current parcel have better target values than any previous non age 0 pool of a different food type } else { break; } parcelIndex++; } // fill resource requests List <ResourceRequest> requests = new List <ResourceRequest>(); foreach (var item in foodParcels.GroupBy(a => a.FoodStore)) { double amount = item.Sum(a => a.Pool.Amount * a.Proportion); if (amount > 0) { double financeLimit = 1; // if obtained from the market make financial transaction before taking ResourcePricing price = item.Key.Price(PurchaseOrSalePricingStyleType.Sale); bool marketIsSource = item.Key.Parent.Parent.Parent == Market; if (bankAccount != null && marketIsSource && price.PricePerPacket > 0) { // finance transaction to buy food from market ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amount / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = $"{TransactionCategory}.PurchaseFood", MarketTransactionMultiplier = 1, RelatesToResource = item.Key.NameWithParent }; bankAccount.Remove(marketRequest); } // is this a market requests.Add(new ResourceRequest() { Resource = item.Key, ResourceType = typeof(HumanFoodStore), AllowTransmutation = false, Required = amount * financeLimit, ResourceTypeName = item.Key.NameWithParent, ActivityModel = this, Category = $"{TransactionCategory}{(marketIsSource?".FromMarket":".FromHousehold")}" }); } } // if still hungry and funds available, try buy food in excess of what stores (private or market) offered using transmutation if present. // This will force the market or private sources to purchase more food to meet demand if transmutation available. // if no market is present it will look to transmutating from its own stores if possible. // this means that other than a purchase from market (above) this activity doesn't need to worry about financial tranactions. int testType = 0; // test is limited to 1 for now so only to metric target NOT intake limit as we use maximum and target values now while (testType < 1 && intake < intakeLimit && (labourActivityFeedTargets.Where(a => !a.TargetAchieved).Any()) && fundsAvailable > 0) { // don't worry about money anymore. The over request will be handled by the transmutation. // move through specified purchase list // 1. to assign based on energy // 2. if still need food assign based on intake still needed metricneeded = 0; LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => !a.TargetAchieved).FirstOrDefault(); if (targetUnfilled != null) { metricneeded = Math.Max(0, (targetUnfilled.Target - targetUnfilled.CurrentAchieved)); double amountToFull = intakeLimit - intake; foreach (LabourActivityFeedTargetPurchase purchase in this.FindAllChildren <LabourActivityFeedTargetPurchase>()) { HumanFoodStoreType foodtype = purchase.FoodStore; if (purchase.ProportionToPurchase > 0 && foodtype != null && (foodtype.TransmutationDefined & intake < intakeLimit)) { double amountEaten = 0; if (testType == 0) { // metric target based on purchase proportion amountEaten = metricneeded / foodtype.ConversionFactor(targetUnfilled.Metric) * purchase.ProportionToPurchase; } else { // amount to satisfy limited by proportion of purchases amountEaten = amountToFull * purchase.ProportionToPurchase; } if (intake + amountEaten > intakeLimit) { amountEaten = intakeLimit - intake; } if (amountEaten > 0) { targetUnfilled.CurrentAchieved += amountEaten * foodtype.ConversionFactor(targetUnfilled.Metric); double amountPurchased = amountEaten / foodtype.EdibleProportion; // update intake.. needed is the amount edible, not the amount purchased. intake += amountEaten; // add financial transactions to purchase from market // if obtained from the market make financial transaction before taking ResourcePricing price = foodtype.Price(PurchaseOrSalePricingStyleType.Sale); if (bankAccount != null) { if (price.PricePerPacket > 0) { ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amountPurchased / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = "Import", MarketTransactionMultiplier = 1, RelatesToResource = foodtype.NameWithParent }; bankAccount.Remove(marketRequest); } else { string warn = $"No price set [{price.PricePerPacket}] for [r={foodtype.Name}] at time of transaction for [a={this.Name}]{Environment.NewLine}No financial transactions will occur.{Environment.NewLine}Ensure price is set or resource pricing file contains entries before this transaction or start of simulation."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } } // find in requests or create a new one ResourceRequest foodRequestFound = requests.Find(a => a.Resource == foodtype); if (foodRequestFound is null) { requests.Add(new ResourceRequest() { Resource = foodtype, ResourceType = typeof(HumanFoodStore), AllowTransmutation = true, Required = amountPurchased, ResourceTypeName = purchase.FoodStoreName, ActivityModel = this, Category = $"{TransactionCategory}.FromImports" }); } else { foodRequestFound.Required += amountPurchased; foodRequestFound.AllowTransmutation = true; } } } } testType++; } } return(requests); }
/// <summary> /// Method used to perform activity if it can occur as soon as resources are available. /// </summary> public override void DoActivity() { this.Status = ActivityStatus.NotNeeded; // get labour shortfall double labourLimit = this.LabourLimitProportion; // get finance purchase shortfall double financeLimit = (spent - earned > 0) ? Math.Min(bankAccount.Amount, spent - earned) / (spent - earned) : 1; if (resourceList.Count() == 0) { if (currentEntries.Count > 0) { this.Status = ActivityStatus.Warning; } return; } else { if (financeLimit < 1) { ResourceRequest purchaseRequest = new ResourceRequest { ActivityModel = this, Required = spent - earned, Available = bankAccount.Amount, Provided = bankAccount.Amount, AllowTransmutation = false, ResourceType = typeof(Finance), ResourceTypeName = AccountName, Category = "External purchases" }; ResourceRequestEventArgs rre = new ResourceRequestEventArgs() { Request = purchaseRequest }; OnShortfallOccurred(rre); } if (financeLimit < 1 || labourLimit < 1) { switch (OnPartialResourcesAvailableAction) { case OnPartialResourcesAvailableActionTypes.ReportErrorAndStop: Summary.WriteWarning(this, $"Ensure resources are available or change OnPartialResourcesAvailableAction setting for activity [a={this.NameWithParent}]"); Status = ActivityStatus.Critical; throw new ApsimXException(this, $"Insufficient resources [r={AccountName}] for activity [a={this.NameWithParent}]"); case OnPartialResourcesAvailableActionTypes.SkipActivity: this.Status = ActivityStatus.Ignored; return; default: break; } this.Status = ActivityStatus.Partial; } else { this.Status = ActivityStatus.Success; } // loop through all resources to exchange and make transactions for (int i = 0; i < currentEntries.Count; i++) { if (resourceList[i] is null) { this.Status = ActivityStatus.Warning; } else { // matching resource was found double amount = Convert.ToDouble(currentEntries[i][fileResource.AmountColumnName], CultureInfo.InvariantCulture); bool isSale = (amount < 0); amount = Math.Abs(amount); ResourcePricing price = null; if (bankAccount != null && !(resourceList[i] is FinanceType)) { price = resourceList[i].Price((amount > 0 ? PurchaseOrSalePricingStyleType.Purchase : PurchaseOrSalePricingStyleType.Sale)); } // transactions if (isSale) { // sell, so limit to labour and amount available amount = Math.Min(amount * labourLimit, resourceList[i].Amount); if (amount > 0) { if (price != null) { double packets = amount / price.PacketSize; if (price.UseWholePackets) { packets = Math.Truncate(packets); amount = packets * price.PacketSize; } bankAccount.Add(packets * price.PricePerPacket, this, (resourceList[i] as CLEMModel).NameWithParent, "External output"); } ResourceRequest sellRequest = new ResourceRequest { ActivityModel = this, Required = amount, AllowTransmutation = false, Category = "External output", RelatesToResource = (resourceList[i] as CLEMModel).NameWithParent }; resourceList[i].Remove(sellRequest); } } else { // limit to labour and financial constraints as this is a purchase amount *= Math.Min(labourLimit, financeLimit); if (amount > 0) { if (price != null) { // need to limit amount by financial constraints double packets = amount / price.PacketSize; if (price.UseWholePackets) { packets = Math.Truncate(packets); } amount = packets * price.PacketSize; ResourceRequest sellRequestDollars = new ResourceRequest { ActivityModel = this, Required = packets * price.PacketSize, AllowTransmutation = false, Category = "External input", RelatesToResource = (resourceList[i] as CLEMModel).NameWithParent }; bankAccount.Remove(sellRequestDollars); } resourceList[i].Add(amount, this, (resourceList[i] as CLEMModel).NameWithParent, "External input"); } } } } } }
private void OnCLEMAnimalSell(object sender, EventArgs e) { RuminantHerd ruminantHerd = Resources.RuminantHerd(); int trucks = 0; double saleValue = 0; double saleWeight = 0; int head = 0; double aESum = 0; // get current untrucked list of animals flagged for sale List <Ruminant> herd = this.CurrentHerd(false).Where(a => a.SaleFlag != HerdChangeReason.None).OrderByDescending(a => a.Weight).ToList(); if (trucking == null) { // no trucking just sell head = herd.Count(); if (herd.Count() > 0) { SetStatusSuccess(); } foreach (var ind in herd) { aESum += ind.AdultEquivalent; saleValue += ind.BreedParams.ValueofIndividual(ind, PurchaseOrSalePricingStyleType.Sale); saleWeight += ind.Weight; ruminantHerd.RemoveRuminant(ind, this); } } else { // if sale herd > min loads before allowing sale if (herd.Select(a => a.Weight / 450.0).Sum() / trucking.Number450kgPerTruck >= trucking.MinimumTrucksBeforeSelling) { // while truck to fill while (herd.Select(a => a.Weight / 450.0).Sum() / trucking.Number450kgPerTruck > trucking.MinimumLoadBeforeSelling) { bool nonloaded = true; trucks++; double load450kgs = 0; // while truck below carrying capacity load individuals foreach (var ind in herd) { if (load450kgs + (ind.Weight / 450.0) <= trucking.Number450kgPerTruck) { nonloaded = false; head++; aESum += ind.AdultEquivalent; load450kgs += ind.Weight / 450.0; saleValue += ind.BreedParams.ValueofIndividual(ind, PurchaseOrSalePricingStyleType.Sale); saleWeight += ind.Weight; ruminantHerd.RemoveRuminant(ind, this); //TODO: work out what to do with suckling calves still with mothers if mother sold. } } if (nonloaded) { Summary.WriteWarning(this, String.Format("There was a problem loading the sale truck as sale individuals did not meet the loading criteria for breed [r={0}]", this.PredictedHerdBreed)); break; } herd = this.CurrentHerd(false).Where(a => a.SaleFlag != HerdChangeReason.None).OrderByDescending(a => a.Weight).ToList(); } // create trucking emissions if (trucks > 0) { SetStatusSuccess(); } trucking.ReportEmissions(trucks, true); } } if (bankAccount != null && head > 0) //(trucks > 0 || trucking == null) { ResourceRequest expenseRequest = new ResourceRequest { ActivityModel = this, AllowTransmutation = false }; // calculate transport costs if (trucking != null) { expenseRequest.Required = trucks * trucking.DistanceToMarket * trucking.CostPerKmTrucking; expenseRequest.Reason = "Transport sales"; bankAccount.Remove(expenseRequest); } foreach (RuminantActivityFee item in Apsim.Children(this, typeof(RuminantActivityFee))) { switch (item.PaymentStyle) { case AnimalPaymentStyleType.Fixed: expenseRequest.Required = item.Amount; break; case AnimalPaymentStyleType.perHead: expenseRequest.Required = head * item.Amount; break; case AnimalPaymentStyleType.perAE: expenseRequest.Required = aESum * item.Amount; break; case AnimalPaymentStyleType.ProportionOfTotalSales: expenseRequest.Required = saleValue * item.Amount; break; default: throw new Exception(String.Format("PaymentStyle ({0}) is not supported for ({1}) in ({2})", item.PaymentStyle, item.Name, this.Name)); } expenseRequest.Reason = item.Name; // uses bank account specified in the RuminantActivityFee item.BankAccount.Remove(expenseRequest); } // add and remove from bank if (saleValue > 0) { bankAccount.Add(saleValue, this, this.PredictedHerdName + " sales"); } } }
private void OnCLEMAnimalSell(object sender, EventArgs e) { Status = ActivityStatus.NoTask; int trucks = 0; double saleValue = 0; double saleWeight = 0; int head = 0; double aESum = 0; // only perform this activity if timing ok, or selling required as a result of forces destock List <Ruminant> herd = new List <Ruminant>(); if (this.TimingOK || this.CurrentHerd(false).Where(a => a.SaleFlag == HerdChangeReason.DestockSale).Any()) { this.Status = ActivityStatus.NotNeeded; // get current untrucked list of animals flagged for sale herd = this.CurrentHerd(false).Where(a => a.SaleFlag != HerdChangeReason.None).OrderByDescending(a => a.Weight).ToList(); } // no individuals to sell if (herd.Count == 0) { return; } List <Ruminant> soldIndividuals = new List <Ruminant>(); if (trucking == null) { // no trucking just sell SetStatusSuccess(); foreach (var ind in herd) { aESum += ind.AdultEquivalent; var pricing = ind.BreedParams.ValueofIndividual(ind, PurchaseOrSalePricingStyleType.Sale); if (pricing != null) { saleValue += pricing.CalculateValue(ind); } saleWeight += ind.Weight; soldIndividuals.Add(ind); HerdResource.RemoveRuminant(ind, this); head++; } } else { // if sale herd > min loads before allowing sale if (herd.Select(a => a.Weight / 450.0).Sum() / trucking.Number450kgPerTruck >= trucking.MinimumTrucksBeforeSelling) { // while truck to fill while (herd.Select(a => a.Weight / 450.0).Sum() / trucking.Number450kgPerTruck > trucking.MinimumLoadBeforeSelling) { bool nonloaded = true; trucks++; double load450kgs = 0; // while truck below carrying capacity load individuals foreach (var ind in herd) { if (load450kgs + (ind.Weight / 450.0) <= trucking.Number450kgPerTruck) { nonloaded = false; head++; aESum += ind.AdultEquivalent; load450kgs += ind.Weight / 450.0; var pricing = ind.BreedParams.ValueofIndividual(ind, PurchaseOrSalePricingStyleType.Sale); if (pricing != null) { saleValue += pricing.CalculateValue(ind); } saleWeight += ind.Weight; soldIndividuals.Add(ind); HerdResource.RemoveRuminant(ind, this); //TODO: work out what to do with suckling calves still with mothers if mother sold. } } if (nonloaded) { Summary.WriteWarning(this, String.Format("There was a problem loading the sale truck as sale individuals did not meet the loading criteria for breed [r={0}]", this.PredictedHerdBreed)); break; } herd = this.CurrentHerd(false).Where(a => a.SaleFlag != HerdChangeReason.None).OrderByDescending(a => a.Weight).ToList(); } // create trucking emissions trucking.ReportEmissions(trucks, true); // if sold all Status = (this.CurrentHerd(false).Where(a => a.SaleFlag != HerdChangeReason.None).Count() == 0) ? ActivityStatus.Success : ActivityStatus.Warning; } } if (bankAccount != null && head > 0) //(trucks > 0 || trucking == null) { ResourceRequest expenseRequest = new ResourceRequest { ActivityModel = this, AllowTransmutation = false }; // calculate transport costs if (trucking != null) { expenseRequest.Required = trucks * trucking.DistanceToMarket * trucking.CostPerKmTrucking; expenseRequest.Category = trucking.TransactionCategory; bankAccount.Remove(expenseRequest); } // perform payments by transaction grouping // uses a list of individuals that were taken from the herd // calculate aEsum and saleValue form the above list for use below // currently done above but can be shifted to calc from grouped indiv // add and remove from bank if (saleValue > 0) { //bankAccount.Add(saleValue, this, this.PredictedHerdName, TransactionCategory); var groupedIndividuals = HerdResource.SummarizeIndividualsByGroups(soldIndividuals, PurchaseOrSalePricingStyleType.Sale); foreach (var item in groupedIndividuals) { foreach (var item2 in item.RuminantTypeGroup) { bankAccount.Add(item2.TotalPrice, this, item.RuminantTypeName, $"{TransactionCategory}.{item2.GroupName}"); } } } // perform activity fee payments foreach (RuminantActivityFee item in this.FindAllChildren <RuminantActivityFee>()) { switch (item.PaymentStyle) { case AnimalPaymentStyleType.Fixed: expenseRequest.Required = item.Amount; break; case AnimalPaymentStyleType.perHead: expenseRequest.Required = head * item.Amount; break; case AnimalPaymentStyleType.perAE: expenseRequest.Required = aESum * item.Amount; break; case AnimalPaymentStyleType.ProportionOfTotalSales: expenseRequest.Required = saleValue * item.Amount; break; default: throw new Exception(String.Format("PaymentStyle [{0}] is not supported for [{1}] in [{2}]", item.PaymentStyle, item.Name, this.Name)); } expenseRequest.Category = item.TransactionCategory; // uses bank account specified in the RuminantActivityFee item.BankAccount.Remove(expenseRequest); } } }
private void OnWFAnimalSell(object sender, EventArgs e) { RuminantHerd ruminantHerd = Resources.RuminantHerd(); Finance Accounts = Resources.FinanceResource() as Finance; FinanceType bankAccount = Accounts.GetFirst() as FinanceType; int trucks = 0; double saleValue = 0; double saleWeight = 0; int head = 0; // get current untrucked list of animals flagged for sale List <Ruminant> herd = ruminantHerd.Herd.Where(a => a.SaleFlag != Common.HerdChangeReason.None & a.Breed == BreedName).OrderByDescending(a => a.Weight).ToList(); // if sale herd > min loads before allowing sale if (herd.Select(a => a.Weight / 450.0).Sum() / Number450kgPerTruck >= MinimumTrucksBeforeSelling) { // while truck to fill while (herd.Select(a => a.Weight / 450.0).Sum() / Number450kgPerTruck > MinimumLoadBeforeSelling) { bool nonloaded = true; trucks++; double load450kgs = 0; // while truck below carrying capacity load individuals foreach (var ind in herd) { if (load450kgs + (ind.Weight / 450.0) <= Number450kgPerTruck) { nonloaded = false; head++; load450kgs += ind.Weight / 450.0; RuminantValue getvalue = PriceList.Where(a => a.Age < ind.Age).OrderBy(a => a.Age).LastOrDefault(); saleValue += getvalue.SellValue * ((getvalue.Style == Common.PricingStyleType.perKg) ? ind.Weight : 1.0); saleWeight += ind.Weight; ruminantHerd.RemoveRuminant(ind); } } if (nonloaded) { Summary.WriteWarning(this, String.Format("There was a problem loading the sale truck as sale individuals did not meet the loading criteria for breed {0}", BreedName)); break; } herd = ruminantHerd.Herd.Where(a => a.SaleFlag != Common.HerdChangeReason.None & a.Breed == BreedName).OrderByDescending(a => a.Weight).ToList(); } if (trucks > 0 & bankAccount != null) { // calculate transport costs double transportCost = trucks * DistanceToMarket * CostPerKmTrucking; bankAccount.Remove(transportCost, this.Name, "Transport"); // calculate MLA fees double mlaCost = head * MLAFees; bankAccount.Remove(mlaCost, this.Name, "R&DFee"); // calculate yard fees double yardCost = head * YardFees; bankAccount.Remove(yardCost, this.Name, "YardCosts"); // calculate commission double commissionCost = saleValue * SalesCommission; bankAccount.Remove(commissionCost, this.Name, "SalesCommission"); // add and remove from bank bankAccount.Add(saleValue, this.Name, "Sales"); } } }
///<inheritdoc/> public bool DoTransmute(ResourceRequest request, double shortfall, double requiredByActivities, ResourcesHolder holder, bool queryOnly) { double transPackets = 0; double shortfallPackets = shortfall / shortfallPacketSize; switch (TransmuteStyle) { case TransmuteStyle.Direct: if ((Parent as Transmutation).UseWholePackets) { shortfallPackets = Math.Ceiling(shortfallPackets); } request.Required = shortfallPackets * AmountPerPacket; break; case TransmuteStyle.UsePricing: if (shortfallPricing.CurrentPrice > 0) { if (transmutePricing.CurrentPrice == 0) { // no value of transmute resource request.Required = 0; } else { if (shortfallWholePackets) { shortfallPackets = Math.Ceiling(shortfallPackets); } request.Required = shortfallPackets * shortfallPricing.CurrentPrice; if (transmutePricing != shortfallPricing && transmutePricing.UseWholePackets) { transPackets = shortfall / transmutePricing.PacketSize; if (transmutePricing.UseWholePackets) { transPackets = Math.Ceiling(transPackets); } } } } break; default: break; } if (queryOnly) { return(request.Required + requiredByActivities <= TransmuteResourceType.Amount); } else { if (TransmuteStyle == TransmuteStyle.UsePricing && !(TransmuteResourceType is FinanceType)) { // add finance transaction to sell transmute financeType.Add(transPackets * transmutePricing.CurrentPrice, request.ActivityModel, TransmuteResourceTypeName, request.Category); // add finance transaction to buy shortfall ResourceRequest financeRequest = new ResourceRequest() { Resource = financeType, Required = shortfallPackets * shortfallPricing.CurrentPrice, RelatesToResource = request.ResourceTypeName, ResourceType = typeof(Finance), ActivityModel = request.ActivityModel, Category = request.Category, }; financeType.Remove(financeRequest); } else { TransmuteResourceType.Remove(request); } } return(true); }
///<inheritdoc/> public bool DoTransmute(ResourceRequest request, double shortfallPacketsNeeded, double requiredByActivities, ResourcesHolder holder, bool queryOnly) { double needed = 0; switch (TransmuteStyle) { case TransmuteStyle.Direct: needed = shortfallPacketSize * AmountPerPacket; break; case TransmuteStyle.UsePricing: needed = shortfallPacketsNeeded * shortfallPricing.CurrentPrice; break; default: break; } // walk through herd based on filters and take those needed to achieve exchange double available = 0; foreach (var group in groupings) { foreach (var ind in group.Filter((ResourceGroup as RuminantHerd).Herd.Where(a => !a.ReadyForSale))) { switch (TransmuteStyle) { case TransmuteStyle.Direct: switch (DirectExhangeStyle) { case PricingStyleType.perHead: available += 1; break; case PricingStyleType.perKg: available += ind.Weight; break; case PricingStyleType.perAE: available += ind.AdultEquivalent; break; default: break; } break; case TransmuteStyle.UsePricing: available += ind.BreedParams.ValueofIndividual(ind, PurchaseOrSalePricingStyleType.Sale)?.CurrentPrice ?? 0; break; default: break; } if (!queryOnly) { // remove individual from herd immediately (ResourceGroup as RuminantHerd).Herd.Remove(ind); } if (available >= needed) { if (queryOnly) { return(true); } break; } } } if (!queryOnly) { if (TransmuteStyle == TransmuteStyle.UsePricing && financeType != null) { // finance transaction from sale of animals financeType.Add(available, request.ActivityModel, TransmuteResourceTypeName, request.Category); // finance transaction from purchase of shortfall ResourceRequest financeRequest = new ResourceRequest() { Resource = financeType, Required = shortfallPacketsNeeded * shortfallPacketSize * shortfallPricing.CurrentPrice, RelatesToResource = request.ResourceTypeName, ResourceType = typeof(Finance), ActivityModel = request.ActivityModel, Category = request.Category, }; financeType.Remove(financeRequest); } } return(true); }