# Project export: ERWIN: Enhanced Rock Weathering Impact Navigator

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: An accessible carbon removal assessment platform for Enhanced Rock Weathering (ERW) projects. Democratizing geochemistry science to inform CO2 removal research and carbon credit investments.
- Devpost: https://devpost.com/software/erwin-enhanced-rock-weathering-impact-navigator
- GitHub: https://github.com/Jackal-Studios/rock-thing
- Demo: https://www.canva.com/design/DAGfPeW-cJw/De_TRIO2zEPGTqJQVsqDMw/view?utm_content=DAGfPeW-cJw&utm_campaign=designshare&utm_medium=link2&utm_source=uniquelinks&utlId=h2678110d5b
- Video: https://www.youtube.com/embed/PkRRfLv6SxA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Sustainability Prize: Best solves the user's pain point of a provided challenge ($800 Cash))
- Team: 3 GitHub contributor(s) — Jackal-Studios (40 commits), Kat Crawford (20 commits), maanitg (6 commits)

## Devpost submission (written by the team)

### Inspiration

Our TreeHacks team formed due to common career motivations in tackling climate change. We’re particularly interested in how technology can shape global finance toward climate solutions, hence our interest in the Carbon Removal Assessment challenge. We spoke with Stanford Earth Systems Professor Kate Maher to learn about pain points in Enhanced Rock Weathering (ERW), an increasingly popular method of carbon removal, where certain species of rock with high CO2 sequestration rates are ground up and distributed to maximize reaction rates via surface area. Forecasting the carbon removal potential of an ERW project is an esoteric process with a steep learning curve - estimates used by investors and project planners in carbon credit markets have vast uncertainty, and more scientific approaches require PhD-level knowledge and access to poorly-documented methods of accessing computational models running on Fortran and text files. There are few people in the world with the resources and experience to scientifically forecast carbon removal rates for ERW projects.

### What it does

We’ve developed a visual platform democratizing ERW carbon removal assessment for any user, focusing on carbon credit markets and ERW researchers. Now, users can select a target region for an ERW project and input simple parameters of feedstock (rock) type project area, feedstock surface density, and time series length for the output. After clicking ‘Run’, the user is presented with graphs of the CO2 removal rate per meter squared, and the pH, over time in years. The user is also presented with an estimate of the total CO2 removed over the total project area over the inputted period of years. Accommodating our two main categories of stakeholders, we have a ‘Basic’ and ‘Advanced’ mode on the User Interface. Those less familiar with the science of ERW, such as potential investors, can choose the Basic mode to be provided with the simple data needed to observe Carbon Removal rates in selected regions. For those who are better-versed in ERW, the Advanced mode serves to provide more options and detailed information regarding the science of ERW and our calculations.

### How we built it

After the user clicks ‘Run,’ ERWIN automatically retrieves a comprehensive set of soil and environmental data for the selected location and integrates it with user-provided inputs to generate and refine the data required for underlying computational algorithms. To do this, we took the latitude and longitude of the center of the user’s region of interest and used soil and weather APIs to fetch data relevant to soil carbon exchange geochemistry, such as soil composition, cation exchange rate, and mean annual precipitation, to name a few. As an additional functionality, before the user selects 'Run,' they have the option to explore quarries recommended for their bounded location. After the user chooses a type of rock and a selected area, the toggle switch on the top left outputs n (set to 50) points with the best quarry options to obtain that rock type. This data comes from the National Mine Map Repository, pulled using Selenium and BeautifulSoup4, and stored in CSV files on a public GitHub repository. These spreadsheet-like files are named with the latitude, longitude, and rock type that they represent, and are accessed as such within the script in JavaScript. One of the greatest challenges of carbon removal projects lies within random sampling and generating probability distributions across vast datasets. Powered by Qiskit in Python, our quantum computing system leverages quantum superposition and entanglement to significantly enhance sampling efficiency and accuracy. Likewise, our quantum processes can run on quantum computers in real-time with IBM Quantum Cloud in the present day, and these processes will become more scalable over time. Identifying the relevant time series outputs across the many output files, based on CO2 removal relevance and scientific interest, we generated distribution curves of these prioritized datasets (ion concentration and soil pH time series), used stoichiometry to convert chemical data to CO2 removal rates, and graphed the mean curves for CO2 removal rates and pH over time with one standard deviation of uncertainty. By integrating the data under the curve, we found the total CO2 removed per m^2 across the time series and multiplied this by the project area to get the total CO2 removed over the provided project timeframe. Overall, our key tasks were: Understanding ERW and carbon removal assessment needs and barriers, for carbon credits markets and researchers, with input from Professor Kate Maher and private sector stakeholders (ERW startup affiliates). Designing and iterating an accessible interface simplifying user input and output. Parsing expected input and output files to identify data types to fetch from the web/APIs, and necessary calculations/conversions to meet the required input template and output needs. Developing an easy-to-use user interface for users with all levels of experience with ERW. Building a Docker container and workflow to standardize operations across operating systems and devices. Challenges: The data fetched and inputted does not correspond directly to the inputs required by the computational algorithms used, and given an example input text file, it took us many hours to reverse engineer the syntax, meanings of different parameters, and units. The lack of documentation served as a frustrating barrier, but also a valuable firsthand insight into the pain point of our intended users - this lack of clarity is the obstacle faced by anyone approaching ERW carbon removal forecasting, from investors to researchers, and the problem we aim to solve. Reading up extensively on soil geochemistry formulas and methodologies and consulting Professor Maher, we were able to parse what the data meant, and the calculations necessary to fit our data to the required inputs. The same process applied to parsing the output files. Accomplishments we’re proud of This was our team’s first hackathon, and we’re incredibly proud of having built a solution to a real-world problem that can drive measurable climate impact, by increasing confidence in carbon removal projects through science-backed forecasts.

### What we learned

We’re particularly proud of learning carbon removal geochemistry on the fly, and teaching ourselves to understand complex academic methodologies to the extent that we were able to build a simplified workflow around the necessary computations - to build a simplified model, we had to understand not only inputs A and outputs B, but also what calculations are required to get from A -> B. Within 36 hours, we built scalable digital infrastructure, a robust UI interface, and a backend interfacing with numerous APIs to fetch soil and climate data, while learning ERW science, formulas, and data representations, and meeting user needs.

### What's next

We seek to continue our collaboration with Professor Maher and other ERW stakeholders in academia and the private sector, to receive more user feedback and better serve stakeholder needs. We also want to expand our offering of recommending ERW project locations for maximizing carbon sequestration and minimizing distance from feedstock quarries, likely supplemented with better feedstock quarry spatial data through computer vision analysis of satellite imagery. The carbon removal project market, and particularly the ERW sector, is rapidly growing, and we see this tool having great potential for the basis of a venture accelerating a zero GHG future.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 15 recognized source files, 87 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 307)

```
.DS_Store
.gitignore
.vscode/launch.json
aEWsoil.in
crunchflow/input/.DS_Store
crunchflow/input/1739724602256efpdatrygl/aEWsoil_0.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_1.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_2.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_3.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_4.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_5.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_6.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_7.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_8.in
crunchflow/input/1739724602256efpdatrygl/aEWsoil_9.in
crunchflow/input/1739724602256efpdatrygl/aqueous.dbs
crunchflow/input/1739724602256efpdatrygl/crunch.rst
crunchflow/input/1739724602256efpdatrygl/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739724602256efpdatrygl/fort.123
crunchflow/input/1739725263891slqdtcwxmc/aqueous.dbs
crunchflow/input/1739725263891slqdtcwxmc/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739725340898dzsjsmqlva/aqueous.dbs
crunchflow/input/1739725340898dzsjsmqlva/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739725401052rmkavjvovz/aqueous.dbs
crunchflow/input/1739725401052rmkavjvovz/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739725808859vgxfrnhqzy/aqueous.dbs
crunchflow/input/1739725808859vgxfrnhqzy/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739731234466rxdvulgesf/aqueous.dbs
crunchflow/input/1739731234466rxdvulgesf/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739731283434vzeotogxag/aqueous.dbs
crunchflow/input/1739731283434vzeotogxag/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/1739733514714kxtsqlfwgu/aqueous.dbs
crunchflow/input/1739733514714kxtsqlfwgu/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/aEWbinary.in
crunchflow/input/aEWsoil.in
crunchflow/input/aqueous.dbs
crunchflow/input/crunch.rst
crunchflow/input/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/ew_simple_binary/.DS_Store
crunchflow/input/ew_simple_binary/aEWbinary.in
crunchflow/input/ew_simple_binary/aqueous.dbs
crunchflow/input/ew_simple_binary/crunch.rst
crunchflow/input/ew_simple_binary/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/ew_simple_binary/fort.123
crunchflow/input/ew_simple_binary/PestControl.ant
crunchflow/input/fort.123
crunchflow/input/PestControl.ant
crunchflow/input/tst.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_0.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_1.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_2.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_3.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_4.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_5.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_6.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_7.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_8.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aEWsoil_9.in
crunchflow/input/tstfold/1739698183160qyapalibqq/aqueous.dbs
crunchflow/input/tstfold/1739698183160qyapalibqq/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/tstfold/1739698183160qyapalibqq/test2.in
crunchflow/input/tstfold/aEWsoil.in
crunchflow/input/tstfold/aqueous.dbs
crunchflow/input/tstfold/crunch.rst
crunchflow/input/tstfold/datacom_DEIbasalt_15April2014_allkinetics.dbs
crunchflow/input/tstfold/fort.123
crunchflow/input/tstfold/test2.in
crunchflow/input/tstfold/tst.in
docker-compose.yml
Dockerfile
Dockerfile.inter
Dockerfile.python
ew_simple_native/.DS_Store
ew_simple_native/aEWbinary.out
ew_simple_native/aEWsoil.in
ew_simple_native/aqueous.dbs
ew_simple_native/area1.out
ew_simple_native/area10.out
ew_simple_native/area11.out
ew_simple_native/area2.out
ew_simple_native/area3.out
ew_simple_native/area4.out
ew_simple_native/area5.out
ew_simple_native/area6.out
ew_simple_native/area7.out
ew_simple_native/area8.out
ew_simple_native/area9.out
ew_simple_native/conc1.out
ew_simple_native/conc10.out
ew_simple_native/conc11.out
ew_simple_native/conc2.out
ew_simple_native/conc3.out
ew_simple_native/conc4.out
ew_simple_native/conc5.out
ew_simple_native/conc6.out
ew_simple_native/conc7.out
ew_simple_native/conc8.out
ew_simple_native/conc9.out
ew_simple_native/crunch.rst
ew_simple_native/CrunchJunk2.out
ew_simple_native/datacom_DEIbasalt_15April2014_allkinetics.dbs
ew_simple_native/exchange1.out
ew_simple_native/exchange10.out
ew_simple_native/exchange11.out
ew_simple_native/exchange2.out
ew_simple_native/exchange3.out
ew_simple_native/exchange4.out
ew_simple_native/exchange5.out
ew_simple_native/exchange6.out
ew_simple_native/exchange7.out
ew_simple_native/exchange8.out
ew_simple_native/exchange9.out
ew_simple_native/fort.123
ew_simple_native/gasdiffflux1.out
ew_simple_native/gasdiffflux10.out
ew_simple_native/gasdiffflux11.out
ew_simple_native/gasdiffflux2.out
ew_simple_native/gasdiffflux3.out
ew_simple_native/gasdiffflux4.out
ew_simple_native/gasdiffflux5.out
[187 more files omitted for size]
```

### Dependencies

- python/requirements.txt: Flask@==2.0.3, numpy@==1.26.4, python-dotenv@==0.19.2p, qiskit@==1.2.4, qiskit-aer@==0.15.1, werkzeug@==2.0.3
- requirements.txt: docker@==6.1.3, Flask@==2.0.3, numpy@==1.26.4, qiskit@==1.2.4, qiskit-aer@==0.15.1, werkzeug@==2.0.3

### Recent commits (newest first)

- added equations for calculating co2 uptake
- litas upload (draft 1)
- docker 0.0.0.0 binding
- api call stuff
- moved api calls to backend
- some unstaged commits from hackathon
- last tweaks
- aaa
- a
- last com les go
- graph deviation added
- added output example layout to parse outpu
- prog bar bad code but works
- loading screeen progress
- fixed simulation errors! + merg
- fixed simulation errors!
- modified runAll
- ...
- ...
- Lots of changes (temp api, etc)

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
Flask==2.0.3
werkzeug==2.0.3 
qiskit==1.2.4 
qiskit-aer==0.15.1 
numpy==1.26.4
docker==6.1.3
```

### docker-compose.yml

```yaml
version: '3.8'

services:
  flask-app:
    build:
      context: .  # Use the current directory for the build
      dockerfile: Dockerfile.python  # Explicitly specify the Dockerfile name (optional if it's named "Dockerfile")
    depends_on:
      - gcc-service
    container_name: flask-app
    networks:
      - my_network
    volumes:
      - ./python:/app/
      - ./crunchflow/input:/home/crunch_user/files  # Bind mount your code to the container (optional)
      - /var/run/docker.sock:/var/run/docker.sock  # Allow access to Docker CLI
    environment:
      - FLASK_APP=/app/app.py  # The name of your Flask app
      - FLASK_ENV=development  # Optional, use 'production' in production
      - PYTHONUNBUFFERED=1 
      - DOCKER_HOST=unix:///var/run/docker.sock  # Specify Docker host
    ports:
      - "5000:5000"  # Expose port 5000 for the Flask app
    user: root 

    group_add:
      - "997"  # docker group ID (may need to adjust this based on your host system)

  gcc-service:
    container_name: topcrunch-custom
    # restart: unless-stopped
    networks:
      - my_network

    build:
      context: .  # Use the current directory for the build
      dockerfile: Dockerfile  # Explicitly specify the Dockerfile name (optional if it's named "Dockerfile")
    command: tail -f /dev/null  # Prevent the container from exiting immediately
    ports:
      - "8080:8080"  # Map port if needed
    volumes:
      - ./crunchflow/input:/home/crunch_user/files  # Bind mount your code to the container (optional)
    working_dir: /home/crunch_user  # Add this to match Dockerfile

networks:
  my_network:
    driver: bridge
    # environment:
    #   - ENV_VAR=example_value  # Pass any environment variables if needed
  # intersystems-service:
  #   container_name: iris-comm

  #   build:
  #     context: .  # Use the current directory for the build
  #     dockerfile: Dockerfile.inter  # Explicitly specify the Dockerfile name (optional if it's named "Dockerfile")
  #   command: tail -f /dev/null  # Prevent the container from exiting immediately
  #   ports:
  #     - "1972:1972" 
  #     - "52773:52773"
  #   environment:
  #     IRIS_PASSWORD: demo
  #     IRIS_USERNAME: demo
  #   volumes:
  #     - ./db:/home/db # Bind mount your code to the container (optional)
  #   # environment:
    

```

### Dockerfile

```

# # Use the official Python image as the base image
# FROM python:3.10-slim

# # Set the working directory inside the container
# WORKDIR /app

# # Copy the current directory contents (including app.py and requirements.txt) into the container at /app
# COPY ./python/. /app

# # Install Flask (and any other dependencies you list in requirements.txt)
# RUN pip install --no-cache-dir -r requirements.txt

# # Expose port 5000 (the default Flask port)
# EXPOSE 5000

# # Define the command to run your Flask app
# CMD ["flask", "run", "--host=0.0.0.0"]


# Use a base image with GCC for ARM64 architecture
FROM docker.io/gcc:14.2

WORKDIR /home/crunch_user

# Install build dependencies
RUN apt-get update && apt-get install -y \
    liblapack-dev python3 python3-pip git make cmake wget && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Set environment variables for PETSc
ENV PETSC_DIR=/home/crunch_user/petsc
ENV PETSC_ARCH=arch-linux-c-opt

# Set environment variables for PETSc
ENV PETSC_DIR=/home/crunch_user/petsc
ENV PETSC_ARCH=arch-linux-c-opt

# Clone, configure, build, and clean up PETSc
RUN git clone https://gitlab.com/petsc/petsc.git $PETSC_DIR && \
    cd $PETSC_DIR && \
    git checkout v3.21.6 && \
    ./configure --with-cc=gcc --with-cxx=g++ --with-fc=gfortran \
                --with-debugging=0 --with-shared-libraries=0 \
                --with-x=0 LIBS=-lstdc++ --with-c2html=0 \
                --with-cxx-dialect=C++11 --with-mpi=0 && \
    make PETSC_DIR=$PETSC_DIR PETSC_ARCH=$PETSC_ARCH all && \
    make PETSC_DIR=$PETSC_DIR PETSC_ARCH=$PETSC_ARCH check && \
    rm -rf $PETSC_DIR/src $PETSC_DIR/arch-$PETSC_ARCH/conf \
           $PETSC_DIR/arch-$PETSC_ARCH/tests $PETSC_DIR/arch-$PETSC_ARCH/share

# Download, build, and clean up CrunchTope
WORKDIR /home/crunch_user
RUN wget https://github.com/CISteefel/CrunchTope/releases/download/v2.10/CrunchTope-2.10.tar.gz && \
    tar -xvzf CrunchTope-2.10.tar.gz && \
    rm CrunchTope-2.10.tar.gz && \
    mv CrunchTope-2.10 crunchtope && \
    cd crunchtope && \
    make && \
    rm -rf /home/crunch_user/crunchtope/*.o /home/crunch_user/crunchtope/Makefile \
           /home/crunch_user/crunchtope/*f.90 && \
    rm -rf /home/crunch_user/petsc

# Add CrunchTope to PATH
ENV PATH="/home/crunch_user/crunchtope:${PATH}"

# Install Python tools
RUN pip3 install --no-cache-dir --break-system-packages \
    jupyterlab \
    crunchflow==2.0.4 \
    pyDGSA==1.0.5 \
    pyEMU==1.3.5 

# # WORKDIR /app
# COPY ./python/. /app

# RUN pip3 install --no-cache-dir --break-system-packages \ 
#     Flask==2.0.3 \
#     werkzeug==2.0.3 \
#     qiskit==1.2.4 \
#     qiskit-aer==0.15.1 \
#     numpy==1.26.4 \
#     docker==6.1.3

# # RUN groupadd -g 998 docker && \
# #     usermod -aG docker root

# # Expose port 5000 (the default Flask port)
# EXPOSE 5000
# CMD ["flask", "run", "--host=0.0.0.0"]

# Final working directory
WORKDIR /home/crunch_user

```

### python/requirements.txt

```
Flask==2.0.3
werkzeug==2.0.3 
qiskit==1.2.4 
qiskit-aer==0.15.1 
numpy==1.26.4
python-dotenv==0.19.2p
```

### python/app.py

```python

from app import create_app
from apicalls import apicalls_bp

app = create_app()

app.register_blueprint(apicalls_bp)

if __name__ == '__main__':
    app.run(debug=True)
```

### python/__init__.py

```python
from flask import Flask
from .routes import main_routes  # Importing routes (views)

def create_app():
    app = Flask(__name__)

    # Set up configurations (can use environment variables, config.py, etc.)
    # app.config.from_object('config.cf')

    # Register blueprints (if you have multiple route files)
    app.register_blueprint(main_routes)

    return app
```

### python/apicalls.py

```python
# apicalls.py

from flask import Blueprint, request, jsonify
from dotenv import load_dotenv
import requests
import os

load_dotenv() 
# Create a blueprint for your API calls
apicalls_bp = Blueprint('apicalls', __name__)

# Load your secret API key securely
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")

@apicalls_bp.route('/api/get-soil-weather', methods=['POST'])
def get_soil_weather():
    data = request.get_json()
    lat = data.get('latitude')
    lng = data.get('longitude')
    print("balls")
    try:
        print("ballsss")
        # 1. Call Soil API (no key required)
        soil_url = f"https://rest.isric.org/soilgrids/v2.0/properties/query?lon={lng}&lat={lat}&property=bdod&property=cec&property=cfvo&property=clay&property=nitrogen&property=ocd&property=ocs&property=phh2o&property=sand&property=silt&property=soc&property=wv0010&property=wv0033&property=wv1500&depth=15-30cm&value=Q0.05&value=Q0.5&value=Q0.95&value=mean&value=uncertainty"
        soil_response = requests.get(soil_url)
        soil_response.raise_for_status()
        soil_data = soil_response.json()

        # 2. Call Weather API (requires key)
        print("ballz")
        weather_url = f"https://history.openweathermap.org/data/2.5/aggregated/year?lat={lat}&lon={lng}&appid={WEATHER_API_KEY}"
        print(WEATHER_API_KEY)
        print("alls")
        weather_response = requests.get(weather_url)
        print("b")
        print("Latitude:", lat, "Longitude:", lng)
        print("Weather API status:", weather_response.status_code)
        print("Weather API response:", weather_response.text)
        weather_response.raise_for_status()
        print("balz")
        weather_data = weather_response.json()
        print("balllls")

        return jsonify({
            'inputs': data,
            'soilData': soil_data,
            'weatherData': weather_data
        })

    except requests.RequestException as e:
        print("errorerdkdlfg")
        return jsonify({'error': str(e)}), 500
```

### python/routes.py

```python
from flask import render_template, Blueprint
from flask import request, jsonify
import subprocess
import crunchflow

main_routes = Blueprint('main', __name__)



# @main_routes.route('/run')
# def run_script():
#     client = docker.from_env()
#     container = client.containers.get('topcrunch-custom')
    
#     if isinstance(commands, str):
#         commands = [commands]
    
#     results = {}
#     for cmd in commands:
#         result = container.exec_run(cmd)
#         results[cmd] = {
#             'exit_code': result.exit_code,
#             'output': result.output.decode('utf-8')
#         }
#     print(result)
#     return result

# docker exec topcrunch-custom bash -c "cd /home/crunch_user/files && CrunchTope aEWbinary.in"
@main_routes.route('/run')
def run_script():
    try:
        # Example command to run in gcc-service
        command = f'docker exec topcrunch-custom bash -c "cd /home/crunch_user/files && ls"'
        
        # Run the command and capture output
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            text=True
        )
        
        # Return the results
        return jsonify({
            'success': True,
            'stdout': result.stdout,
            'stderr': result.stderr,
            'exit_code': result.returncode
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500
    

@main_routes.route('/run1')
def run_script2():
    return crunchflow.create_user_folder("1HELLLO")
    # return crunchflow.run_command('bash -c "cd /home/crunch_user && ls"')

@main_routes.route('/rundel')
def run_script3():
    return crunchflow.delete_user_folder("1HELLLO")
    # return crunchflow.run_command('bash -c "cd /home/crunch_user && ls"')
@main_routes.route('/runcrfol')
def run_script4():
    return crunchflow.create_input_folder(1, crunchflow.generate_unique_filename())
    
@main_routes.route('/runparseout')
def run_script5():
    return crunchflow.get_output(1)      
    
@main_routes.route('/ls')
def run_script6():
    return crunchflow.ls_user_folder("")      
    

@main_routes.route('/')
def home():
    crunchflow.hello()
    return render_template('home.html')


@main_routes.route('/save_soil_data', methods=['POST'])
def save_soil_data():
    data = request.json
    # print(data)
    # sent the output to the parse output and return

    # resp = crunchflow.get_output(data)
    resp = crunchflow.handle_json_request(data)

    return resp

@main_routes.route('/save_data', methods=['POST'])
def save_data():
    data = request.json
    # print(data)
    # sent the output to the parse output and return

    # resp = crunchflow.get_output(data)
    # resp = crunchflow.handle_json_request(data)
    resp = crunchflow.handle_json_request(data)

    return resp
    

# @main_routes.route('/tst')
# def about():
#     return render_template('tst.html')
```

### python/liming.py

```python


# User inputs crop, user inputs lat long, how much lime needed? (LiTAS)


import math

def get_LR(crop, lat, long):
    # Table of crops with their target acidity saturation percentages
    crop_acidity_table = {
        "maize": 20,
        "soybean": 15,
        "wheat": 25,
        "coffee": 35,
        "rice": 25,
        "banana": 35,
        "eucalyptus": 45,
        "pine": 55,
        "sugarcane": 25,
        "cassava": 40,
        "tomato": 15,
        "tea": 30,
        "potato": 20,
        "forage grass": 35,
        "oil palm": 30
    }

    # Convert input to lowercase to handle case-insensitivity
    crop = crop.lower()

    # FETCH SOIL DATA w LAT LONG
    ECEC_i = get_ECEC(lat, long)
    ea_i = get_EA(lat, long)

    # Check if the crop exists in the table and return the corresponding target acidity saturation
    if crop in crop_acidity_table:
        lr = calculate_lr(crop_acidity_table[crop], ECEC_i, ea_i, a=0.6, b=0.92)
    else:
        raise ValueError(f"Crop '{crop}' not found. Please choose a valid crop from the list.")

    return lr




def calculate_lr(TAS, ECEC_i, ea_i, a, b):
    """
    Calculate lime requirement using LiTAS model (acid saturation approach)
    
    Parameters:
    a = cmolc of exchangeable acidity neutralized per cmolc of CaCO3
    b = 
    exch_acid_i (float): Initial exchangeable acidity [cmolc/kg]
    ECEC_i (float): Initial Effective Cation Exchange Capacity [cmolc/kg]
    TAS (float): Target Acidity Saturation [%] (default=10)
    """
    TASp = TAS/100
    LR_cmol = (ea_i - TASp * ECEC_i)/( a + TASp + (b - a) )
    return LR_cmol 



# given rock feedstock/biochar and lime requirement, how much feedstock needed?

def calculate_rock_volume(liming_rate, feedstock):
    # Hardcoded rock properties (density in kg/m³, lime equivalent in cmolc/kg)
    rock_properties = {
        "basalt": {"density": 2800, "lime_equivalence_per_kg": 0.5},
        "olivine": {"density": 3300, "lime_equivalence_per_kg": 0.6},
        # Add more rocks as needed
    }
    
    # Check if the feedstock is valid
    if feedstock not in rock_properties:
        raise ValueError(f"Feedstock '{feedstock}' not recognized. Please use a valid feedstock (e.g., basalt, olivine).")
    
    # Get the properties of the chosen feedstock
    rock_density = rock_properties[feedstock]["density"]
    lime_equivalence_per_kg = rock_properties[feedstock]["lime_equivalence_per_kg"]
    
    # Calculate how much rock mass is needed to provide the required lime (in kg)
    rock_mass_needed_kg = liming_rate / lime_equivalence_per_kg
    
    # Calculate the volume of rock required using the formula: Volume = Mass / Density
    rock_volume_needed_m3 = rock_mass_needed_kg / rock_density
    
    return rock_volume_needed_m3




# given this feedstock, how much CO2 removed? (Dietzen model)

def calculate_co2_uptake(soil_ph, pco2_uatm, cation_difference_kg_ha, temperature=15):
    """
    Calculate CO2 uptake from enhanced weathering of silicate minerals applied to soils
   
    Parameters:
    -----------
    soil_ph : float
        Soil pH measured in water (not CaCl2)
    pco2_uatm : float
        Soil CO2 partial pressure in μatm (microatmospheres)
    cation_difference_kg_ha : dict
        Dictionary containing differences in exchangeable cation content (kg/ha) between
        treated and control plots. Keys should be 'Mg', 'Ca', 'K', 'Na'
    temperature : float, optional
        Soil temperature in °C (default: 15°C)
       
    Returns:
    --------
    dict
        Dictionary containing:
        - 'x_star': Correction factor for non-carbonic acids
        - 'co2_uptake_mg': CO2 uptake for each cation in kg/ha
        - 'total_co2_uptake': Total CO2 uptake in kg/ha
    """
    import math
   
    # Convert pH to [H+] concentration
    h_plus = 10 ** (-soil_ph)
   
    # Convert pCO2 from μatm to atm and calculate [H2CO3]
    pco2_atm = pco2_uatm / 1000000
    h2co3 = pco2_atm / 29.41  # Henry's constant
   
    # Calculate K1 (equilibrium constant)
    # The paper uses K1 = 10^-6.415 at 15°C
    # This is a simplified approach; a more rigorous approach would include temperature effects
    k1 = 10 ** (-6.415)
   
    # Calculate X* correction factor (Equation 8)
    # This accounts for weathering by non-carbonic soil acids
    term1 = 10**(-20) - (10**(-10) * k1 * h2co3 / h_plus) - 10**(-10) * h_plus
    term2_inside = (10**(-10) * k1 * h2co3 / h_plus + 10**(-10) * h_plus - 10**(-20))**2
    term2_inside += 4 * 10**(-30) * k1 * h2co3 / h_plus
    term2 = math.sqrt(term2_inside)
    x_star = (term1 + term2) / (2 * 10**(-20))
   
    # Cap X* at 1.0 (it shouldn't exceed 1.0 in theory)
    x_star = min(x_star, 1.0)
   
    # Calculate CO2 uptake for each cation (Equation 9)
    co2_uptake = {}
    total_co2 = 0
   
    # Molar masses and charges of cations for calculating CO2 uptake
    cation_properties = {
        'Mg': {'charge': 2, 'atomic_mass': 24.305},
        'Ca': {'charge': 2, 'atomic_mass': 40.078},
        'K': {'charge': 1, 'atomic_mass': 39.098},
        'Na': {'charge': 1, 'atomic_mass': 22.990}
    }
   
    for cation, diff_kg_ha in cation_difference_kg_ha.items():
        if cation in cation_properties:
            # Convert cation mass to moles
            atomic_mass = cation_properties[cation]['atomic_mass']
            charge = cation_properties[cation]['charge']
           
            # Convert kg/ha to mol/ha of charge
            mol_charge_ha = (diff_kg_ha * 1000 / atomic_mass) * charge
           
            # Calculate CO2 uptake (Equation 9)
            co2_kg_ha = x_star * mol_charge_ha * 44.01 / 1000
            co2_uptake[cation] = co2_kg_ha
            total_co2 += co2_kg_ha
   
    return {
        'x_star': x_star,
        'co2_uptake_kg': co2_uptake,
        'total_co2_uptake': total_co2
    }

def calculate_co2_uptake(lr, soil_ph, pco2_uatm, depth = 0.25, lat, long):
    """
    Calculate CO2 uptake from enhanced weathering of silicate miner
[truncated — 2104 more characters]
```

### python/crunchflow.py

```python
import os
import time
import random, string
import subprocess
import numpy as np
from flask import request, jsonify
import json
from quantumdeviation import quantum
from scipy.interpolate import interp1d


def hello():
    print("hi")


def run_command(command):
    try:
        full_command = f"docker exec topcrunch-custom {command}"
        result = subprocess.run(
            full_command,
            shell=True,
            capture_output=True,
            text=True
        )
        print(result)
        
        return jsonify({
            'success': True,
            'stdout': result.stdout,
            'stderr': result.stderr,
            'exit_code': result.returncode
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500



def read_and_package(file_path, columns):
    # Load data, skip first line (metadata) and header line
    data = np.loadtxt(file_path, skiprows=2)
    
    # Get column indices (based on the file format you showed)
    column_indices = {
        'Time(yrs)': 0,
        'pH': 1,
        'H+': 2,
        'CO2(aq)': 3,
        'Mg++': 4,
        'Ca++': 5
    }
    
    # Create dictionary with requested columns
    result = {
        col: data[:, column_indices[col]].tolist()
        for col in columns
    }
    
    return json.dumps(result)


def create_user_folder(foldername):
    return run_command(f'bash -c "cd /home/crunch_user/files && mkdir -p {foldername} && cp *.dbs {foldername}/ && ls {foldername}"')

def delete_user_folder(foldername):
    return run_command(f'bash -c "cd /home/crunch_user/files && rm -rf {foldername} && ls /home/crunch_user/files"')

def ls_user_folder(foldername):
    return run_command(f'bash -c "cd /home/crunch_user/files/{foldername} && ls"')




def randomword(length):
   letters = string.ascii_lowercase
   return ''.join(random.choice(letters) for i in range(length))

def current_milli_time():
    return round(time.time() * 1000)

def generate_unique_filename():
    return str(current_milli_time()) + randomword(10)

def create_input_file(years, feedstock, claypercent, siltpercent, temp, precip, cec, 
                          feedspread, bulkdense, file_num, foldername, sandpercent):

    claydense = 2.65
    siltdense = 2.60
    sanddense = 2.65
    # rates and density for feedstocks
    mineral_rates = {
        "Basalt": (-13.00, 3.01),
        "HCl(c)": (-10.00, 1.03),
        "CO2_pump": (-6.00, 1.98),
        "HNO3(c)": (-10.00, 1.51),
        "Cs-Illite": (-5.00, 275),
        "Gypsum": (-6.00, 2.3),
        "Calcite": (-6.19, 2.71),
        "Pyrite": (-8.00, 5),
        "Vivianite": (-8.0, 2.65),
        "Hydroxylapatite": (-11.0, 3.05),
        "Katoite": (-9, 2.79),
        "Portlandite": (-9, 2.23),
        "Aragonite": (-8.10, 2.93),
        "Dolomite": (-7.70, 2.84),
        "Magnesite": (-9.40, 3.05),
        "Siderite": (-8.90, 3.96),
        "Iron": (-11.30, 7.86),
        "Muscovite": (-13.00, 2.85),
        "Quartz": (-13.39, 2.65),
        "Kaolinite": (-13.00, 2.65),
        "Ilmenite": (-13.00, 4.45),
        "K-Feldspar": (-13.00, 2.60),
        "Gibbsite": (-10.00, 2.34),
        "Chalcedony": (-13.39, 2.62),
        "Ettringite": (-8.00, 1.80),
        "Chrysocolla": (-7.00, 2.10),
        "Goethite": (-7.00, 4.0),
        "Jarosite": (-6.00, 3.25),
        "Jurbanite": (-6.00, 1.79),
        "Alunite": (-7.00, 2.75),
        "Wollastonite": (-13.00, 2.84),
        "Larnite": (-13.00, 3.28)
    }

    # feedstock ="Larnite"
    
    rate = mineral_rates[feedstock.title()][0]
    feeddense = mineral_rates[feedstock.title()][1]

    print(rate)
    print(feeddense)

    # Perform calculations
    temp = temp - 273
    constant_flow = precip / 2 #(precip / 1000) / 2  TODO update with new weather
    soilclay_volfrac = (claypercent * claydense) / bulkdense
    soilsilt_volfrac = (siltpercent * siltdense) / bulkdense
    soilsand_volfrac = (sandpercent * sanddense) / bulkdense
    stock_volfrac = ((float(feedspread)/10)/float(feeddense)) / (bulkdense + ((float(feedspread)/10)/float(feeddense)))
    stock_clayfrac = ((claypercent * claydense) / (bulkdense + ((float(feedspread)/10)/float(feeddense))))
    stock_siltfrac = ((siltpercent * siltdense) / (bulkdense + ((float(feedspread)/10)/float(feeddense))))
    stock_sandfrac = ((sandpercent * sanddense) / (bulkdense + ((float(feedspread)/10)/float(feeddense))))

    stock_sandfrac = round((stock_sandfrac / 100), 3)
    stock_siltfrac/= 100
    stock_clayfrac/= 100
    stock_volfrac/= 100
    soilsand_volfrac/= 100
    soilsilt_volfrac/= 100
    soilclay_volfrac /= 100

    #normalize
    # tot = soilsand_volfrac + soilsilt_volfrac + soilclay_volfrac
    # soilsand_volfrac /=tot
    # soilsilt_volfrac /=tot
    # soilclay_volfrac /=tot


    # Read the template file
    # with open('/home/crunch_user/files/aEWsoil.in', 'r') as file:
    #     content = file.read()
    print("reading lines")
    with open('/home/crunch_user/files/aEWsoil.in', "r") as file:
        lines = file.readlines()

    print("read lines")
    years = 200
    lines[28] = f"time_series    timeEWm{file_num}.out {years}\n"

    # does float work?
    if(temp > 25):
        lines[53] = f"set_temperature {temp:.1f}\n"
    
    # if constant_flow < 0.1:
    constant_flow = 0.2
    lines[84] = f'constant_flow   {constant_flow:.1f}\n'

    lines[92] = f'{cec:.1f} cmol/kg\n'

    if(feedstock.lower() == "basalt"):
        feedstock = 'An50Ab50AS'


    #35 padding should be 
    lines[145] = f"{feedstock}"
    len_rock = len(feedstock)
    while(35 - len_rock != 0):
        lines[145] += " "
        len_rock += 1
    lines[145] += f"{stock_volfrac:.9f} ssa  0.5\n" 

    lines[147] = lines[147][:35]  + f"{stock_clayfrac:.9f} ssa 0.1\n"
    
    lines[148] = lines[148][:35]  + f"{stock_siltfrac:.9f} ssa 0.1\n"
    lines[149] = lines[149][:35]  + f"{stock_sandfrac:.9f} ssa 0.1\n"

    # TODO maybe line 157 t
[truncated — 7030 more characters]
```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]