Commit 071d432
Changed files (3)
src
domain
test
src/domain/domain.csproj
@@ -41,6 +41,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Greeting.cs" />
+ <Compile Include="Well.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition=" '$(OS)' == 'Windows_NT' " />
src/domain/Well.cs
@@ -0,0 +1,470 @@
+namespace domain
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ public class Gas : ICommodity
+ {
+ public Percent PercentageFrom(IComposition composition)
+ {
+ return composition.PercentageFor<Gas>();
+ }
+ }
+
+ public class Oil : ICommodity
+ {
+ public Percent PercentageFrom(IComposition composition)
+ {
+ return composition.PercentageFor<Oil>();
+ }
+ }
+
+ public class NGL : ICommodity
+ {
+ public Percent PercentageFrom(IComposition composition)
+ {
+ return composition.PercentageFor<NGL>();
+ }
+ }
+
+ public class Condensate : ICommodity
+ {
+ public Percent PercentageFrom(IComposition composition)
+ {
+ return composition.PercentageFor<Condensate>();
+ }
+ }
+
+ public class All : ICommodity
+ {
+ public Percent PercentageFrom(IComposition composition)
+ {
+ return composition.PercentageFor<Gas>()
+ .Plus(composition.PercentageFor<Oil>())
+ .Plus(composition.PercentageFor<NGL>())
+ .Plus(composition.PercentageFor<Condensate>());
+ }
+ }
+
+ public interface ICommodity
+ {
+ Percent PercentageFrom(IComposition composition);
+ }
+
+ public class DrillSchedule
+ {
+ ICollection<IWell> wells = new List<IWell>();
+
+ public void Include(IWell well)
+ {
+ wells.Add(well);
+ }
+
+ public IQuantity EstimatedGrossProductionFor<Commodity>(Month month) where Commodity : ICommodity, new()
+ {
+ IQuantity result = new Quantity(0, new BOED());
+ Accept(well =>
+ {
+ result = result.Plus(well.GrossProductionFor<Commodity>(month));
+ });
+ return result;
+ }
+
+ public IQuantity EstimatedNetProductionFor<Commodity>(Month month) where Commodity : ICommodity, new()
+ {
+ IQuantity result = new Quantity(0, new BOED());
+ Accept(well =>
+ {
+ result = result.Plus(well.NetProductionFor<Commodity>(month));
+ });
+ return result;
+ }
+
+ void Accept(Action<IWell> visitor )
+ {
+ wells.Each(well =>
+ {
+ visitor(well);
+ });
+ }
+ }
+
+ public interface IComposition
+ {
+ void SplitFor<Commodity>(Percent percent) where Commodity : ICommodity;
+ IQuantity PercentageOf<Commodity>(IQuantity quantity) where Commodity : ICommodity, new();
+ Percent PercentageFor<Commodity>() where Commodity : ICommodity;
+ }
+
+ public class DeclineCurve
+ {
+ IDictionary<int, IQuantity> production = new Dictionary<int, IQuantity>();
+ IComposition split = new CommoditySplits();
+
+ public void Add(int month, IQuantity quantity)
+ {
+ production[month] = quantity;
+ }
+
+ public TypeCurve StartingOn(Month initialProductionMonth)
+ {
+ return new TypeCurve(CreateProductionFor(initialProductionMonth));
+ }
+
+ IEnumerable<Production> CreateProductionFor(Month initialProductionMonth)
+ {
+ foreach (var quantity in production)
+ yield return new Production(initialProductionMonth.Plus(quantity.Key), quantity.Value, split);
+ }
+
+ public void Composition<Commodity>(Percent percent) where Commodity : ICommodity
+ {
+ split.SplitFor<Commodity>(percent);
+ }
+ }
+
+ public class CommoditySplits : IComposition
+ {
+ IDictionary<Type, Percent> splits = new Dictionary<Type, Percent>();
+
+ public void SplitFor<Commodity>(Percent percent) where Commodity : ICommodity
+ {
+ splits[typeof (Commodity)] = percent;
+ }
+
+ public IQuantity PercentageOf<Commodity>(IQuantity quantity) where Commodity : ICommodity, new()
+ {
+ return new Commodity().PercentageFrom(this).Reduce(quantity);
+ }
+
+ public Percent PercentageFor<Commodity>() where Commodity : ICommodity
+ {
+ return splits.ContainsKey(typeof(Commodity)) ? splits[typeof (Commodity)] : Percent.Zero;
+ }
+ }
+
+ public class Production
+ {
+ Month month;
+ IQuantity produced;
+ IComposition split;
+
+ public Production(Month month, IQuantity produced, IComposition split)
+ {
+ this.month = month;
+ this.produced = produced;
+ this.split = split;
+ }
+
+ public bool IsFor(Month otherMonth)
+ {
+ return month.Equals(otherMonth);
+ }
+
+ public IQuantity ProductionOf<T>() where T : ICommodity, new()
+ {
+ return split.PercentageOf<T>(produced);
+ }
+ }
+
+ public class TypeCurve
+ {
+ IEnumerable<Production> production;
+
+ public TypeCurve(IEnumerable<Production> production)
+ {
+ this.production = production.ToList();
+ }
+
+ public IQuantity ProductionFor<Commodity>(Month month) where Commodity : ICommodity, new()
+ {
+ return production.Single(x => x.IsFor(month)).ProductionOf<Commodity>();
+ }
+ }
+
+ public interface IQuantity
+ {
+ IQuantity Plus(IQuantity other);
+ IQuantity ConvertTo(IUnitOfMeasure units);
+ decimal Amount { get; }
+ IUnitOfMeasure Units { get; }
+ }
+
+ public class Oppurtunity
+ {
+ Percent workingInterest;
+ DeclineCurve declineCurve;
+
+ public Oppurtunity()
+ {
+ workingInterest = 100m.Percent();
+ }
+
+ public void WorkingInterest(Percent percent)
+ {
+ workingInterest = percent;
+ }
+
+ public void DeclinesUsing(DeclineCurve declineCurve)
+ {
+ this.declineCurve = declineCurve;
+ }
+
+ public IWell BringOnlineOn(Month initialProductionMonth)
+ {
+ return new Well(initialProductionMonth, workingInterest, declineCurve.StartingOn(initialProductionMonth));
+ }
+
+ public IEnumerable<IWell> BringOnlineOn(Month initialProductionMonth, int numberOfWells)
+ {
+ for (var i = 0; i < numberOfWells; i++)
+ yield return BringOnlineOn(initialProductionMonth);
+ }
+ }
+
+ public class Well : IWell
+ {
+ Month initialProductionMonth;
+ Percent workingInterest;
+ TypeCurve curve;
+
+ public Well(Month initialProductionMonth, Percent workingInterest, TypeCurve curve)
+ {
+ this.initialProductionMonth = initialProductionMonth;
+ this.workingInterest = workingInterest;
+ this.curve = curve;
+ }
+
+ public IQuantity GrossProductionFor<Commodity>(Month month) where Commodity : ICommodity, new()
+ {
+ return curve.ProductionFor<Commodity>(month);
+ }
+
+ public IQuantity NetProductionFor<Commodity>(Month month) where Commodity : ICommodity, new()
+ {
+ return workingInterest.Reduce(GrossProductionFor<Commodity>(month));
+ }
+ }
+
+ public class Month
+ {
+ readonly int year;
+ readonly int month;
+
+ public Month(int year, int month)
+ {
+ this.year = year;
+ this.month = month;
+ }
+
+ public bool Equals(Month other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.year == year && other.month == month;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Month)) return false;
+ return Equals((Month) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (year*397) ^ month;
+ }
+ }
+
+ public Month Plus(int months)
+ {
+ var newMonth = new DateTime(year, month, 01).AddMonths(months);
+ return new Month(newMonth.Year, newMonth.Month);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0} {1}", year, month);
+ }
+ }
+
+ public interface IWell
+ {
+ IQuantity GrossProductionFor<T>(Month month) where T : ICommodity, new();
+ IQuantity NetProductionFor<T>(Month month) where T : ICommodity, new();
+ }
+
+ public class Percent
+ {
+ readonly decimal percentage;
+ public static Percent Zero = new Percent(0);
+
+ public Percent(decimal percentage)
+ {
+ this.percentage = percentage;
+ }
+
+ public bool Equals(Percent other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.percentage == percentage;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Percent)) return false;
+ return Equals((Percent) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return percentage.GetHashCode();
+ }
+
+ public IQuantity Reduce(IQuantity original)
+ {
+ //return new ProratedQuantity(original, this);
+ return new Quantity(PortionOf(original.Amount), original.Units);
+ }
+
+ public Percent Plus(Percent other)
+ {
+ return new Percent(percentage + other.percentage);
+ }
+
+ public decimal PortionOf(decimal amount)
+ {
+ return amount*percentage;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0} %", percentage);
+ }
+ }
+
+ public static class Units
+ {
+ public static Percent Percent(this decimal percentage)
+ {
+ return new Percent(percentage/100);
+ }
+
+ public static IQuantity BOED(this int quantity)
+ {
+ return BOED(Convert.ToDecimal(quantity));
+ }
+
+ public static IQuantity BOED(this decimal quantity)
+ {
+ return new Quantity(quantity, new BOED());
+ }
+ }
+
+ public class BOED : IUnitOfMeasure
+ {
+ public decimal Convert(decimal amount, IUnitOfMeasure units)
+ {
+ // need to do actual conversion here;
+ return amount;
+ }
+
+ public bool Equals(BOED other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return true;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (BOED)) return false;
+ return Equals((BOED) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (name != null ? name.GetHashCode() : 0);
+ }
+
+ public override string ToString()
+ {
+ return name;
+ }
+
+ readonly string name = "BOED";
+ }
+
+ public class Quantity : IQuantity
+ {
+ public Quantity(decimal amount, IUnitOfMeasure units)
+ {
+ Amount = amount;
+ Units = units;
+ }
+
+ public decimal Amount { get;private set; }
+
+ public IUnitOfMeasure Units { get; private set; }
+
+ public IQuantity Plus(IQuantity other)
+ {
+ return new Quantity(Amount + other.ConvertTo(Units).Amount, Units);
+ }
+
+ public IQuantity ConvertTo(IUnitOfMeasure unitOfMeasure)
+ {
+ return new Quantity(unitOfMeasure.Convert(Amount, this.Units), unitOfMeasure);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0} {1}", Amount, Units);
+ }
+
+ public bool Equals(Quantity other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.Amount == Amount && Equals(other.Units, Units);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Quantity)) return false;
+ return Equals((Quantity) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Amount.GetHashCode()*397) ^ (Units != null ? Units.GetHashCode() : 0);
+ }
+ }
+ }
+
+ public interface IUnitOfMeasure
+ {
+ decimal Convert(decimal amount, IUnitOfMeasure units);
+ }
+ public static class Iterating
+ {
+ public static void Each<T>(this IEnumerable<T> items, Action<T> visitor){
+ foreach (var item in items ?? Enumerable.Empty<T>())
+ visitor(item);
+ }
+ }
+}
src/test/WellSpecs.cs
@@ -1,105 +1,51 @@
-namespace test
-{
- using System.Collections.Generic;
- using Machine.Specifications;
- using domain;
-
- public class WellSpecs
- {
- Establish context = ()=>
- {
- //sut = new Well();
- };
-
- public class when_projecting_production_from_a_new_well
- {
- It should_calculate_the_correct_projection =()=>
- {
- var well = new Well(new Month(2013, 01));
- well.apply_split_for<Oil>(25.Percent());
- well.apply_split_for<Gas>(25.Percent());
- well.apply_split_for<NGL>(25.Percent());
- well.apply_split_for<Condensate>(25.Percent());
-
- var decline = new DeclineCurve();
- decline.for_month(1,new BOED(100));
- well.use(decline);
-
- well.production_for<Oil>(new Month(2013, 01)).ShouldEqual(new BOED(25));
- };
- }
-
- static readonly Well sut;
- }
- public class DeclineCurve{
- IDictionary<int, BOED> curve;
- public DeclineCurve(){
- curve = new Dictionary<int, BOED>();
- }
- public void for_month(int month, BOED volume){
- curve[month] = volume;
- }
- //public void each(Action<int, BOED> action){
- //}
- }
-
- public class BOED{
- decimal volume;
- public BOED(decimal volume){
- this.volume = volume;
- }
- public bool Equals(BOED other){
- if(ReferenceEquals(null, other)) return false;
- return other.volume == this.volume;
- }
- public override bool Equals(object other){
- if(ReferenceEquals(null, other)) return false;
- if(!(other is BOED)) return false;
- return Equals((BOED)other);
- }
- public override string ToString(){
- return string.Format("{0} BOED", volume);
- }
- }
- public class Well
- {
- Month ip_month;
- IDictionary<ICommodity, Percent> splits;
- IList<Projection> projections;
-
- public Well(Month expected_initial_production_month){
- this.ip_month = expected_initial_production_month;
- this.splits = new Dictionary<ICommodity, Percent>();
- this.projections = new List<Projection>();
- }
- public void apply_split_for<Product>(Percent percent) where Product : ICommodity, new()
- {
- splits.Add(new Product(), percent);
- }
- public void use(DeclineCurve curve){
- //curve.each( (month,total_volume) => {
- //projections.Add( splits.Select((split)=> split.create_projection(ip_month.add(month), total_volume));
- //});
- }
- public BOED production_for<Commodity>(Month month){
- return new BOED(25);
- }
- }
- public class Projection{}
- public class Percent
- {
- }
- public class Month{
- public Month(int year, int month){}
- }
- public interface ICommodity{}
- public class Oil : ICommodity{}
- public class Gas : ICommodity{}
- public class NGL : ICommodity{}
- public class Condensate : ICommodity{}
- public static class Conversion{
- public static Percent Percent(this int percent){
- return new Percent();
- }
- }
-}
+namespace test
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using Machine.Specifications;
+ using domain;
+
+ public class WellSpecs
+ {
+ public class when_estimating_production
+ {
+ It should_be_able_to_tell_the_estimated_total_production_for_any_month= () =>
+ {
+ var parkland100Percent = new Oppurtunity();
+ parkland100Percent.WorkingInterest(100m.Percent());
+ var declineCurve = new DeclineCurve();
+ declineCurve.Composition<Gas>(100m.Percent());
+ declineCurve.Add(0, 100.BOED());
+ parkland100Percent.DeclinesUsing(declineCurve);
+
+ var schedule = new DrillSchedule();
+ var jan2013 = new Month(2013, 01);
+ schedule.Include(parkland100Percent.BringOnlineOn(jan2013));
+ schedule.EstimatedGrossProductionFor<All>(jan2013).ShouldEqual(100.BOED());
+ schedule.EstimatedGrossProductionFor<Gas>(jan2013).ShouldEqual(100.BOED());
+ schedule.EstimatedGrossProductionFor<Oil>(jan2013).ShouldEqual(0.BOED());
+ schedule.EstimatedGrossProductionFor<NGL>(jan2013).ShouldEqual(0.BOED());
+ schedule.EstimatedGrossProductionFor<Condensate>(jan2013).ShouldEqual(0.BOED());
+ };
+
+ It should_be_able_to_tell_the_estimated_net_total_production_for_any_month = () =>
+ {
+ var parkland75Percent = new Oppurtunity();
+ parkland75Percent.WorkingInterest(75m.Percent());
+ var declineCurve = new DeclineCurve();
+ declineCurve.Composition<Gas>(50m.Percent());
+ declineCurve.Composition<Oil>(50m.Percent());
+ declineCurve.Add(0, 100.BOED());
+ parkland75Percent.DeclinesUsing(declineCurve);
+
+ var schedule = new DrillSchedule();
+ var jan2013 = new Month(2013, 01);
+ schedule.Include(parkland75Percent.BringOnlineOn(jan2013));
+ schedule.EstimatedNetProductionFor<All>(jan2013).ShouldEqual(75.BOED());
+ schedule.EstimatedNetProductionFor<Gas>(jan2013).ShouldEqual(37.5m.BOED());
+ schedule.EstimatedNetProductionFor<Oil>(jan2013).ShouldEqual(37.5m.BOED());
+ };
+ }
+ }
+}