Пример #1
0
 private void OnCommencing(object sender, EventArgs e)
 {
     ruminantHerd = resources.FindResourceGroup <RuminantHerd>();
     if (ruminantHerd is null)
     {
         return;
     }
 }
Пример #2
0
        private void OnCommencing(object sender, EventArgs e)
        {
            ruminantHerd = resources.FindResourceGroup <RuminantHerd>();

            // determine any herd filtering
            herdFilters = new List <RuminantGroup>();
            IModel current = this;

            while (current.GetType() != typeof(ZoneCLEM))
            {
                var filtergroup = current.Children.OfType <RuminantGroup>();
                if (filtergroup.Count() > 1)
                {
                    Summary.WriteWarning(this, "Multiple ruminant filter groups have been supplied for [" + current.Name + "]" + Environment.NewLine + "Only the first filter group will be used.");
                }
                if (filtergroup.FirstOrDefault() != null)
                {
                    herdFilters.Insert(0, filtergroup.FirstOrDefault());
                }
                current = current.Parent as IModel;
            }

            // get full name for reporting
            current = this;
            string name = this.Name;

            while (current.GetType() != typeof(ZoneCLEM))
            {
                string quoteName = (current.GetType() == typeof(ActivitiesHolder)) ? "[" + current.Name + "]":current.Name;
                name    = quoteName + "." + name;
                current = current.Parent as IModel;
            }
        }
Пример #3
0
 private void OnStartOfSimulation(object sender, EventArgs e)
 {
     ResourceGroup         = resources.FindResourceGroup <Labour>();
     shortfallPacketSize   = (Parent as Transmutation).TransmutationPacketSize;
     shortfallWholePackets = (Parent as Transmutation).UseWholePackets;
     groupings             = this.FindAllChildren <RuminantGroup>().ToList <object>();
 }
Пример #4
0
 /// <summary>
 /// A common method to get the labour resource requests from the activity.
 /// </summary>
 /// <returns></returns>
 protected List<ResourceRequest> GetLabourResourcesNeededForActivity()
 {
     List<ResourceRequest> labourResourceRequestList = new List<ResourceRequest>();
     foreach (LabourRequirement item in FindAllChildren<LabourRequirement>())
     {
         GetDaysLabourRequiredReturnArgs daysResult = GetDaysLabourRequired(item);
         if (daysResult.DaysNeeded > 0)
         {
             foreach (LabourFilterGroup fg in item.FindAllChildren<LabourFilterGroup>())
             {
                 int numberOfPpl = 1;
                 if (item.ApplyToAll)
                     // how many matches
                     numberOfPpl = fg.Filter(Resources.FindResourceGroup<Labour>().Items).Count();
                 for (int i = 0; i < numberOfPpl; i++)
                 {
                     labourResourceRequestList.Add(new ResourceRequest()
                     {
                         AllowTransmutation = true,
                         Required = daysResult.DaysNeeded,
                         ResourceType = typeof(Labour),
                         ResourceTypeName = "",
                         ActivityModel = this,
                         FilterDetails = new List<object>() { fg },
                         Category = daysResult.Category,
                         RelatesToResource = daysResult.RelatesToResource
                     }
                     ); ;
                 }
             }
         }
     }
     return labourResourceRequestList;
 }
Пример #5
0
        /// <summary>
        /// Validate model
        /// </summary>
        /// <param name="validationContext"></param>
        /// <returns></returns>
        public IEnumerable <ValidationResult> Validate(ValidationContext validationContext)
        {
            ruminantHerd = resources.FindResourceGroup <RuminantHerd>();
            var results = new List <ValidationResult>();

            // check that this activity has a parent of type CropActivityManageProduct

            if (ruminantHerd is null)
            {
                string[] memberNames = new string[] { "Missing resource" };
                results.Add(new ValidationResult($"No ruminant herd resource could be found for [ReportRuminantHerd] [{this.Name}]", memberNames));
            }
            if (!this.FindAllChildren <RuminantGroup>().Any())
            {
                string[] memberNames = new string[] { "Missing ruminant filter group" };
                results.Add(new ValidationResult($"The [ReportRuminantHerd] [{this.Name}] requires at least one filter group to identify individuals to report", memberNames));
            }
            return(results);
        }
Пример #6
0
        private void OnStartOfSimulation(object sender, EventArgs e)
        {
            // get herd for transmutating
            ResourceGroup             = resources.FindResourceGroup <RuminantHerd>();
            TransmuteResourceTypeName = ResourceGroup.Name;
            shortfallPacketSize       = (Parent as Transmutation).TransmutationPacketSize;
            shortfallWholePackets     = (Parent as Transmutation).UseWholePackets;
            groupings = ResourceGroup.FindAllChildren <RuminantGroup>();

            var shortfallResourceType = this.FindAncestor <IResourceType>();

            if (shortfallResourceType != null && TransmuteStyle == TransmuteStyle.UsePricing)
            {
                shortfallPricing = shortfallResourceType.Price(PurchaseOrSalePricingStyleType.Purchase);
                if (FinanceTypeForTransactionsName != "No transactions")
                {
                    // link to first bank account
                    financeType = resources.FindResourceType <Finance, FinanceType>(this, FinanceTypeForTransactionsName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportWarning);
                }
            }
        }
Пример #7
0
        /// <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);
        }
Пример #8
0
        private void OnCommencing(object sender, EventArgs e)
        {
            int lossModifier = 1;

            if (ReportLossesAsNegative)
            {
                lossModifier = -1;
            }

            // check if running from a CLEM.Market
            bool market = (FindAncestor <Zone>().GetType() == typeof(Market));

            List <string> variableNames = new List <string>
            {
                "[Clock].Today as Date"
            };

            if (IncludeFinancialYear)
            {
                Finance financeStore = resources.FindResourceGroup <Finance>();
                if (financeStore != null)
                {
                    variableNames.Add($"[Resources].{financeStore.Name}.FinancialYear as FY");
                }
            }

            List <string> eventNames = new List <string>();

            if (ResourceGroupsToReport != null && ResourceGroupsToReport.Trim() != "")
            {
                // check it is a ResourceGroup
                CLEMModel model = resources.FindResource <ResourceBaseWithTransactions>(ResourceGroupsToReport);
                if (model == null)
                {
                    summary.WriteMessage(this, String.Format("Invalid resource group [{0}] in ReportResourceBalances [{1}]\r\nEntry has been ignored", this.ResourceGroupsToReport, this.Name), MessageType.Warning);
                }
                else
                {
                    bool pricingIncluded = false;
                    if (model.GetType() == typeof(RuminantHerd))
                    {
                        pricingIncluded = model.FindAllDescendants <AnimalPricing>().Where(a => a.Enabled).Count() > 0;

                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.ID as uID");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Breed as Breed");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Sex as Sex");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Age as Age");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Weight as Weight");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.AdultEquivalent as AE");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.SaleFlag as Category");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Class as Class");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.HerdName as RelatesTo");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.PopulationChangeDirection as Change");

                        // ToDo: add pricing for ruminants including buy and sell pricing
                        // Needs update in CLEMResourceTypeBase and link between ResourcePricing and AnimalPricing.
                    }
                    else
                    {
                        pricingIncluded = model.FindAllDescendants <ResourcePricing>().Where(a => a.Enabled).Count() > 0;

                        if (ReportStyle == ReportTransactionStyle.GainAndLossColumns)
                        {
                            variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Gain as Gain");
                            variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.Loss * {lossModifier} as Loss");
                        }
                        else
                        {
                            variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.TransactionType as Type");
                            variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.AmountModifiedForLoss({ReportLossesAsNegative}) as Amount");
                        }

                        // get all converters for this type of resource
                        if (IncludeConversions)
                        {
                            var converterList = model.FindAllDescendants <ResourceUnitsConverter>().Select(a => a.Name).Distinct();
                            if (converterList != null)
                            {
                                foreach (var item in converterList)
                                {
                                    if (ReportStyle == ReportTransactionStyle.GainAndLossColumns)
                                    {
                                        variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\",\"gain\",{ReportLossesAsNegative}) as {item}_Gain");
                                        variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\",\"loss\",{ReportLossesAsNegative}) as {item}_Loss");
                                    }
                                    else
                                    {
                                        variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\", {ReportLossesAsNegative}) as {item}_Amount");
                                    }
                                }
                            }
                        }

                        // add pricing
                        if (IncludePrice && pricingIncluded)
                        {
                            if (ReportStyle == ReportTransactionStyle.GainAndLossColumns)
                            {
                                variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$gain\",\"gain\", {ReportLossesAsNegative}) as Price_Gain");
                                variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$loss\",\"loss\", {ReportLossesAsNegative}) as Price_Loss");
                            }
                            else
                            {
                                variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$gain\", {ReportLossesAsNegative}) as Price_Amount");
                            }
                        }

                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.ResourceType.Name as Resource");
                        // if this is a multi CLEM model simulation then add a new column with the parent Zone name
                        if (FindAncestor <Simulation>().FindChild <Market>() != null)
                        {
                            variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Activity.CLEMParentName as Source");
                        }
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Activity.Name as Activity");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.RelatesToResource as RelatesTo");
                        variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Category as Category");
                    }
                }
                eventNames.Add("[Resources]." + this.ResourceGroupsToReport + ".TransactionOccurred");
            }

            VariableNames = variableNames.ToArray();
            EventNames    = eventNames.ToArray();
            SubscribeToEvents();
        }
        /// <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.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>().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>().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)
                {
                    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.FindResourceGroup <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 && cost > fundsAvailable)
                        //{
                        //    // need to get some more money

                        //    // sell cattle based on selling groups till run out of cattle or meet shortfall

                        //    // adjust fundsAvailable with new money

                        //}
                        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                    = TransactionCategory,
                            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           = TransactionCategory
                    });
                }
            }

            // 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 = base.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>())
                {
                    HumanFoodStoreType foodtype = resourcesHolder.FindResourceType <HumanFoodStore, HumanFoodStoreType>(this, purchase.FoodStoreName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore);
                    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);
                            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           = TransactionCategory
                                });
                            }
                            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);
        }