Last active
July 14, 2025 16:15
-
-
Save PartTimeLegend/da36e19f58092e090080fadde187b4a1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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