Skip to content

Instantly share code, notes, and snippets.

@PartTimeLegend
Last active July 14, 2025 16:15
Show Gist options
  • Save PartTimeLegend/da36e19f58092e090080fadde187b4a1 to your computer and use it in GitHub Desktop.
Save PartTimeLegend/da36e19f58092e090080fadde187b4a1 to your computer and use it in GitHub Desktop.
import math
from typing import Dict, List, Tuple
from dataclasses import dataclass
#!/usr/bin/env python3
"""
Pond Planning Application
Calculates pond capacity, fish stocking, and equipment sizing using metric measurements
"""
@dataclass
class Fish:
"""Represents a fish species with its characteristics"""
name: str
adult_length_cm: float
bioload_factor: float # waste production multiplier
min_liters_per_fish: int
@dataclass
class PondDimensions:
"""Pond dimensions and shape"""
length_meters: float
width_meters: float
avg_depth_meters: float
shape: str = "rectangular" # rectangular, circular, oval, kidney, L-shaped, triangular, hexagonal, octagonal, irregular
class PondPlanner:
"""Main pond planning calculator"""
# Common fish species with their requirements
FISH_DATABASE = {
"goldfish": Fish("Goldfish", 20, 1.0, 75),
"koi": Fish("Koi", 60, 2.5, 950),
"shubunkin": Fish("Shubunkin", 30, 1.2, 285),
"orfe": Fish("Golden Orfe", 45, 1.5, 380),
"tench": Fish("Tench", 40, 1.3, 380),
"rudd": Fish("Rudd", 35, 1.1, 285),
"ide": Fish("Ide", 50, 1.6, 475),
"barbel": Fish("Barbel", 75, 2.8, 1140),
"carp": Fish("Common Carp", 80, 3.0, 1520),
"grass_carp": Fish("Grass Carp", 100, 3.5, 1900),
"sturgeon": Fish("Sturgeon", 120, 4.0, 2850),
"catfish": Fish("Channel Catfish", 65, 2.2, 760),
"bitterling": Fish("Bitterling", 8, 0.5, 38),
"dace": Fish("Dace", 15, 0.8, 57),
"roach": Fish("Roach", 25, 0.9, 95),
"chub": Fish("Chub", 40, 1.4, 285),
"perch": Fish("Perch", 30, 1.3, 190),
"pike": Fish("Pike", 90, 3.2, 1710),
"sunfish": Fish("Bluegill Sunfish", 20, 1.0, 95),
"mosquitofish": Fish("Mosquitofish", 6, 0.3, 19),
"weather_loach": Fish("Weather Loach", 25, 0.7, 95),
"sterlet": Fish("Sterlet", 60, 2.0, 570),
# New fish species
"gudgeon": Fish("Gudgeon", 12, 0.6, 45),
"minnow": Fish("Minnow", 10, 0.5, 38),
"bleak": Fish("Bleak", 15, 0.7, 57),
"bream": Fish("Common Bream", 50, 1.8, 475),
"silver_bream": Fish("Silver Bream", 25, 1.0, 190),
"crucian_carp": Fish("Crucian Carp", 30, 1.2, 285),
"leather_carp": Fish("Leather Carp", 70, 2.7, 1140),
"mirror_carp": Fish("Mirror Carp", 75, 2.8, 1330),
"ghost_carp": Fish("Ghost Carp", 55, 2.3, 855),
"golden_tench": Fish("Golden Tench", 40, 1.3, 380),
"golden_rudd": Fish("Golden Rudd", 35, 1.1, 285),
"blue_orfe": Fish("Blue Orfe", 45, 1.5, 380),
"silver_orfe": Fish("Silver Orfe", 45, 1.5, 380),
"sarasa_comet": Fish("Sarasa Comet", 25, 1.1, 190),
"comet_goldfish": Fish("Comet Goldfish", 30, 1.2, 285),
"fantail_goldfish": Fish("Fantail Goldfish", 15, 0.9, 114),
"white_cloud_minnow": Fish("White Cloud Mountain Minnow", 4, 0.2, 15),
"japanese_rice_fish": Fish("Japanese Rice Fish (Medaka)", 4, 0.2, 15),
"golden_medaka": Fish("Golden Medaka", 4, 0.2, 15),
"fathead_minnow": Fish("Fathead Minnow", 8, 0.4, 30),
"rosy_red_minnow": Fish("Rosy Red Minnow", 8, 0.4, 30),
"pond_loach": Fish("Pond Loach", 20, 0.8, 76),
"stone_loach": Fish("Stone Loach", 12, 0.6, 45),
"spined_loach": Fish("Spined Loach", 10, 0.5, 38),
"three_spined_stickleback": Fish("Three-spined Stickleback", 6, 0.3, 23),
"ten_spined_stickleback": Fish("Ten-spined Stickleback", 5, 0.3, 19),
"pumpkinseed": Fish("Pumpkinseed Sunfish", 18, 0.9, 95),
"largemouth_bass": Fish("Largemouth Bass", 70, 2.5, 1330),
"smallmouth_bass": Fish("Smallmouth Bass", 50, 2.0, 760),
"yellow_perch": Fish("Yellow Perch", 25, 1.1, 190),
"walleye": Fish("Walleye", 60, 2.2, 950),
"northern_pike": Fish("Northern Pike", 100, 3.5, 1900),
"muskie": Fish("Muskellunge", 130, 4.2, 3325),
"chain_pickerel": Fish("Chain Pickerel", 40, 1.6, 475),
"brook_trout": Fish("Brook Trout", 35, 1.4, 475),
"rainbow_trout": Fish("Rainbow Trout", 40, 1.6, 570),
"brown_trout": Fish("Brown Trout", 45, 1.8, 665),
"arctic_char": Fish("Arctic Char", 50, 1.9, 760),
"grayling": Fish("Grayling", 30, 1.3, 285),
"whitefish": Fish("Whitefish", 35, 1.4, 380),
"cisco": Fish("Cisco", 25, 1.1, 190),
"burbot": Fish("Burbot", 50, 1.8, 665),
"eel": Fish("European Eel", 80, 2.8, 1520),
"lamprey": Fish("River Lamprey", 30, 1.2, 285),
"paddlefish": Fish("Paddlefish", 150, 4.5, 5700),
"gar": Fish("Longnose Gar", 90, 3.0, 1710),
"bowfin": Fish("Bowfin", 70, 2.6, 1140),
"buffalo_fish": Fish("Bigmouth Buffalo", 80, 2.7, 1520),
"sucker": Fish("White Sucker", 45, 1.6, 570),
"redhorse": Fish("River Redhorse", 50, 1.8, 665),
"drum": Fish("Freshwater Drum", 60, 2.1, 950),
"mooneye": Fish("Mooneye", 25, 1.0, 190),
"goldeye": Fish("Goldeye", 30, 1.2, 285),
"shad": Fish("Gizzard Shad", 35, 1.3, 380),
"alewife": Fish("Alewife", 20, 0.9, 114),
"smelt": Fish("Rainbow Smelt", 15, 0.7, 76)
}
def __init__(self):
self.dimensions = None
self.fish_stock = {} # fish_type: quantity
def set_dimensions(self, length: float, width: float, depth: float, shape: str = "rectangular"):
"""Set pond dimensions"""
self.dimensions = PondDimensions(length, width, depth, shape)
def calculate_volume_liters(self) -> float:
"""Calculate pond volume in liters"""
if not self.dimensions:
raise ValueError("Pond dimensions not set")
shape = self.dimensions.shape.lower()
length = self.dimensions.length_meters
width = self.dimensions.width_meters
depth = self.dimensions.avg_depth_meters
# Calculate volume in cubic meters based on shape
if shape == "circular":
# Treat width as diameter
radius = width / 2
volume_m3 = math.pi * radius * radius * depth
elif shape == "rectangular":
volume_m3 = length * width * depth
elif shape == "oval":
# Ellipse formula: Ο€ * a * b where a and b are semi-axes
volume_m3 = math.pi * (length / 2) * (width / 2) * depth
elif shape == "kidney":
# Approximation: 75% of equivalent rectangle
volume_m3 = length * width * depth * 0.75
elif shape == "l-shaped":
# Approximation: 70% of equivalent rectangle (assuming typical L-shape)
volume_m3 = length * width * depth * 0.70
elif shape == "triangular":
# Triangle area = 0.5 * base * height
volume_m3 = 0.5 * length * width * depth
elif shape == "hexagonal":
# Regular hexagon area = (3√3/2) * s² where s is side length
# Approximating with length as diagonal, width as side
side = width
volume_m3 = (3 * math.sqrt(3) / 2) * side * side * depth
elif shape == "octagonal":
# Regular octagon area = 2(1+√2) * s² where s is side length
# Approximating with width as side length
side = width
volume_m3 = 2 * (1 + math.sqrt(2)) * side * side * depth
elif shape == "teardrop":
# Approximation: 65% of equivalent oval
volume_m3 = math.pi * (length / 2) * (width / 2) * depth * 0.65
elif shape == "figure-8":
# Approximation: 60% of equivalent rectangle
volume_m3 = length * width * depth * 0.60
elif shape == "star":
# Approximation: 55% of equivalent rectangle
volume_m3 = length * width * depth * 0.55
elif shape == "crescent":
# Approximation: 45% of equivalent rectangle
volume_m3 = length * width * depth * 0.45
else:
# For irregular or unknown shapes, use rectangular approximation with 80% factor
volume_m3 = length * width * depth * 0.8
# Convert to liters (1 cubic meter = 1000 liters)
return volume_m3 * 1000
def add_fish(self, fish_type: str, quantity: int):
"""Add fish to the stock"""
if fish_type.lower() not in self.FISH_DATABASE:
raise ValueError(f"Unknown fish type: {fish_type}")
self.fish_stock[fish_type.lower()] = self.fish_stock.get(fish_type.lower(), 0) + quantity
def remove_fish(self, fish_type: str, quantity: int):
"""Remove fish from the stock"""
if fish_type.lower() in self.fish_stock:
self.fish_stock[fish_type.lower()] = max(0, self.fish_stock[fish_type.lower()] - quantity)
if self.fish_stock[fish_type.lower()] == 0:
del self.fish_stock[fish_type.lower()]
def calculate_required_volume(self) -> float:
"""Calculate minimum volume needed for current fish stock"""
total_liters = 0
for fish_type, quantity in self.fish_stock.items():
fish = self.FISH_DATABASE[fish_type]
total_liters += fish.min_liters_per_fish * quantity
return total_liters
def calculate_bioload(self) -> float:
"""Calculate total bioload (waste production factor)"""
total_bioload = 0
for fish_type, quantity in self.fish_stock.items():
fish = self.FISH_DATABASE[fish_type]
total_bioload += fish.bioload_factor * quantity
return total_bioload
def calculate_pump_size(self) -> Tuple[int, str]:
"""Calculate required pump flow rate in LPH (Liters Per Hour)"""
if not self.dimensions:
raise ValueError("Pond dimensions not set")
volume = self.calculate_volume_liters()
bioload = self.calculate_bioload()
# Base turnover: complete water change every 2 hours
base_flow = volume / 2
# Adjust for bioload (higher bioload needs more circulation)
bioload_multiplier = 1 + (bioload / 10) # 10% increase per bioload point
required_lph = int(base_flow * bioload_multiplier)
# Recommendation category
if bioload <= 5:
category = "Light bioload"
elif bioload <= 15:
category = "Medium bioload"
else:
category = "Heavy bioload"
return required_lph, category
def calculate_filter_size(self) -> Dict[str, str]:
"""Calculate filter requirements"""
volume = self.calculate_volume_liters()
bioload = self.calculate_bioload()
# Biological filter volume (10-15% of pond volume for heavy bioload)
bio_filter_percent = min(15, 5 + bioload)
bio_filter_liters = int(volume * bio_filter_percent / 100)
# UV sterilizer wattage (1 watt per 190-380 liters depending on bioload)
uv_watts_per_liter = 1/285 if bioload <= 10 else 1/190
uv_watts = int(volume * uv_watts_per_liter)
return {
"biological_filter": f"{bio_filter_liters} liters filter media",
"uv_sterilizer": f"{uv_watts} watts",
"mechanical_filter": "Pre-filter with 50-100 micron capability"
}
def get_stocking_recommendations(self) -> Dict[str, int]:
"""Get recommended maximum fish counts for current pond"""
if not self.dimensions:
raise ValueError("Pond dimensions not set")
volume = self.calculate_volume_liters()
recommendations = {}
for fish_type, fish in self.FISH_DATABASE.items():
max_count = int(volume / fish.min_liters_per_fish)
recommendations[fish.name] = max_count
return recommendations
def get_available_shapes(self) -> List[str]:
"""Get list of available pond shapes"""
return [
"rectangular", "circular", "oval", "kidney", "l-shaped",
"triangular", "hexagonal", "octagonal", "teardrop",
"figure-8", "star", "crescent", "irregular"
]
def get_fish_types_list(self) -> List[str]:
"""Get ordered list of fish types for numbered selection"""
return sorted(self.FISH_DATABASE.keys())
def generate_report(self) -> str:
"""Generate comprehensive pond planning report"""
if not self.dimensions:
return "Error: Pond dimensions not set"
volume = self.calculate_volume_liters()
required_volume = self.calculate_required_volume()
bioload = self.calculate_bioload()
pump_lph, pump_category = self.calculate_pump_size()
filter_specs = self.calculate_filter_size()
recommendations = self.get_stocking_recommendations()
report = f"""
POND PLANNING REPORT
====================
Pond Specifications:
- Dimensions: {self.dimensions.length_meters}m x {self.dimensions.width_meters}m x {self.dimensions.avg_depth_meters}m
- Shape: {self.dimensions.shape.title()}
- Total Volume: {volume:,.0f} liters
Current Fish Stock:
"""
if self.fish_stock:
for fish_type, quantity in self.fish_stock.items():
fish = self.FISH_DATABASE[fish_type]
report += f"- {fish.name}: {quantity} fish\n"
report += f"""
Stocking Analysis:
- Required Volume: {required_volume:,.0f} liters
- Available Volume: {volume:,.0f} liters
- Status: {'βœ“ Adequate' if volume >= required_volume else '⚠ Overstocked'}
- Total Bioload: {bioload:.1f}
"""
else:
report += "- No fish currently stocked\n"
report += f"""
Equipment Recommendations:
- Pump Size: {pump_lph:,} LPH ({pump_category})
- {filter_specs['biological_filter']}
- UV Sterilizer: {filter_specs['uv_sterilizer']}
- Mechanical Filter: {filter_specs['mechanical_filter']}
Maximum Stocking Recommendations:
"""
for fish_name, max_count in recommendations.items():
report += f"- {fish_name}: {max_count} fish max\n"
return report
def main():
"""Interactive pond planning session"""
planner = PondPlanner()
print("🐟 Pond Planning Application 🐟")
print("=" * 40)
# Get pond dimensions
print("\nEnter pond dimensions:")
length = float(input("Length (meters): "))
width = float(input("Width (meters): "))
depth = float(input("Average depth (meters): "))
print("\nAvailable shapes:")
shapes = planner.get_available_shapes()
for i, shape in enumerate(shapes, 1):
print(f"{i:2d}. {shape}")
shape_input = input(f"Shape (1-{len(shapes)} or name) [rectangular]: ").strip()
if shape_input.isdigit():
shape_index = int(shape_input) - 1
if 0 <= shape_index < len(shapes):
shape = shapes[shape_index]
else:
shape = "rectangular"
else:
shape = shape_input.lower() if shape_input else "rectangular"
planner.set_dimensions(length, width, depth, shape)
# Fish stocking
print("\nAvailable fish types:")
fish_types = planner.get_fish_types_list()
for i, fish_type in enumerate(fish_types, 1):
fish = planner.FISH_DATABASE[fish_type]
print(f"{i:2d}. {fish_type}: {fish.name} ({fish.min_liters_per_fish} L/fish)")
print(f"\nAdd fish to your pond (enter number 1-{len(fish_types)} or fish name):")
print("Press Enter with empty input to finish:")
while True:
fish_input = input("Fish type: ").strip()
if not fish_input:
break
# Check if input is a number
if fish_input.isdigit():
fish_index = int(fish_input) - 1
if 0 <= fish_index < len(fish_types):
selected_fish = fish_types[fish_index]
else:
print(f"Invalid number! Please enter 1-{len(fish_types)}")
continue
else:
# Check if input matches a fish name
fish_input_lower = fish_input.lower()
if fish_input_lower in planner.FISH_DATABASE:
selected_fish = fish_input_lower
else:
print("Unknown fish type! Please try again.")
continue
# Get quantity and add fish
try:
fish_display_name = planner.FISH_DATABASE[selected_fish].name
quantity = int(input(f"Number of {fish_display_name}: "))
planner.add_fish(selected_fish, quantity)
print(f"Added {quantity} {fish_display_name} to pond")
except ValueError:
print("Please enter a valid number for quantity")
# Generate and display report
print("\n" + planner.generate_report())
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment