Skip to main content
Now that the Python Flask application works locally, the next step is preparing the repository for a public release. A production-ready repo typically includes:
  • A curated .gitignore to avoid committing virtual environments, IDE files, and secrets
  • A clear, comprehensive README.md with install and usage instructions
  • Inline documentation and docstrings in source files
  • At least a basic test or smoke test
  • Clean HTML templates and static assets
  • Clear repository metadata (LICENSE, contributor guidance)
  • Optional: CI configuration, requirements.txt, and deployment instructions (Docker, Heroku, etc.)
Below are concrete examples and edits that make a simple METAR Reader repository ready to publish on GitHub.

README excerpt (example)

A small Flask app that fetches METAR (aviation weather reports) and translates cryptic METAR codes into human-readable weather summaries.

Features
- Temperature and dewpoint (Celsius ↔ Fahrenheit conversion)
- Visibility conditions
- Weather phenomena (rain, snow, fog, etc.)
- Cloud coverage and altitude
- Barometric pressure
- Observation time

To run the application (Unix/macOS):
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export FLASK_APP=app.py
flask run
```text

On Windows (PowerShell):
```powershell
python -m venv venv
venv\Scripts\Activate.ps1
pip install -r requirements.txt
$env:FLASK_APP = "app.py"
flask run
```text

Then open: http://127.0.0.1:5000

Examples of airport codes:
- KHIO, KLAX, KJFK

Example METAR:
- METAR: `KHIO 051953Z 36008KT 10SM CLR 21/M01 A3012`
- Human translation: "Clear skies, 70°F (21°C), wind from the north at 8 knots, 10+ miles visibility, pressure 30.12 inHg"

HTML template (cleaned)

Use semantic markup and minimal inline attributes. Keep the form simple and accessible.
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>METAR Reader</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body>
  <div class="container">
    <form method="POST" action="/metar" class="metar-form">
      <input type="text" name="station" placeholder="Enter ICAO code (e.g. KJFK)" required />
      <button type="submit">Get Weather Report</button>
    </form>

    <div class="info">
      <h3>What is METAR?</h3>
      <p>METAR is a standardized weather report format used in aviation. This tool converts the cryptic METAR codes into plain English so you can easily understand current weather conditions at any airport.</p>
    </div>
  </div>
</body>
</html>

app.py (cleaned and documented core)

This example focuses on a readable, well-documented METAR decoder and minimal Flask routes for demonstration. Add error handling and XML parsing for production data fetches.
"""
A small Flask web application that fetches METAR reports and converts them
into human-readable text summaries.

Author: Jeremy Morgan
License: MIT
"""

from flask import Flask, render_template, request
import requests
import re

app = Flask(__name__)


class METARDecoder:
    """
    Decode METAR weather reports into a human-readable summary.
    """

    def __init__(self):
        # 16-point compass abbreviations mapped to plain English
        self._compass = [
            'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
            'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
        ]
        self.wind_directions = {
            'N': 'north', 'NNE': 'north-northeast', 'NE': 'northeast', 'ENE': 'east-northeast',
            'E': 'east', 'ESE': 'east-southeast', 'SE': 'southeast', 'SSE': 'south-southeast',
            'S': 'south', 'SSW': 'south-southwest', 'SW': 'southwest', 'WSW': 'west-southwest',
            'W': 'west', 'WNW': 'west-northwest', 'NW': 'northwest', 'NNW': 'north-northwest'
        }

    def get_wind_direction_text(self, degrees):
        """
        Convert numeric wind direction in degrees to a plain-English direction.
        Expects degrees as an int (0-360). Treat 0 or 360 as 'north'.
        """
        try:
            deg = int(degrees) % 360
        except (TypeError, ValueError):
            return "variable"

        # Determine sector index for 16-point compass (22.5° sectors)
        index = int((deg + 11.25) / 22.5) % 16
        abbr = self._compass[index]
        return self.wind_directions.get(abbr, "variable")

    def decode_visibility(self, vis_str):
        """Decode visibility reported in statute miles (e.g. '10SM')."""
        match = re.match(r'(\d+)(SM)', vis_str)
        if match:
            miles = int(match.group(1))
            if miles >= 10:
                return "10+ miles visibility"
            return f"{miles} statute miles visibility"
        return "visibility not reported"

    def decode_weather_phenomena(self, wx_str):
        """
        Map common METAR weather codes to plain English.
        Supports RA, SN, DZ, FG, BR, HZ, TS, SH, and combinations.
        """
        mapping = {
            'RA': 'rain', 'SN': 'snow', 'DZ': 'drizzle', 'FG': 'fog',
            'BR': 'mist', 'HZ': 'haze', 'TS': 'thunderstorms', 'SH': 'showers'
        }
        found = []
        for code, desc in mapping.items():
            if code in wx_str:
                found.append(desc)
        return ", ".join(found) if found else None

    def decode_clouds(self, cloud_str):
        """
        Decode cloud layer tokens such as FEW030, SCT045, BKN100, OVC008, CLR/SKC.
        Convert 3-digit cloud bases to feet (hundreds of feet -> multiply by 100).
        """
        if cloud_str in ('CLR', 'SKC'):
            return "clear skies"
        match = re.search(r'(FEW|SCT|BKN|OVC)(\d{3})', cloud_str)
        if match:
            coverage = match.group(1)
            altitude = int(match.group(2)) * 100  # Convert hundreds of feet to feet
            description = {
                'FEW': 'few clouds',
                'SCT': 'scattered clouds',
                'BKN': 'broken clouds',
                'OVC': 'overcast'
            }.get(coverage, coverage)
            return f"{description} at {altitude} feet"
        return "cloud conditions not reported"

    def decode_metar(self, metar):
        """
        Parse a METAR string and return a structured dictionary and a short summary.
        This is a simple, forgiving parser sufficient for typical METARs used in the app.
        """
        decoded = {}
        parts = metar.split()
        # Example METAR header: KHPN 051953Z 36008KT 10SM CLR 21/M01 A3012
        # Basic parsing loop:
        for part in parts:
            # Wind (e.g., 36008KT or VRB03KT)
            if re.match(r'^\d{3}\d{2}KT$|^VRB\d{2}KT$', part):
                # Extract direction and speed
                if part.startswith('VRB'):
                    wind_dir_text = 'variable'
                    speed = part[3:5]
                else:
                    wind_deg = int(part[0:3])
                    wind_dir_text = self.get_wind_direction_text(wind_deg)
                    speed = part[3:5]
                decoded['wind'] = f"Wind from the {wind_dir_text} at {int(speed)} knots"

            # Visibility in statute miles, e.g. 10SM
            elif part.endswith('SM'):
                decoded['visibility'] = self.decode_visibility(part)

            # Weather phenomena tokens
            elif any(wx in part for wx in ['RA', 'SN', 'DZ', 'FG', 'BR', 'HZ', 'TS', 'SH']):
                weather = self.decode_weather_phenomena(part)
                if weather:
                    decoded['weather'] = weather

            # Cloud coverage tokens
            elif any(part.startswith(cloud) for cloud in ['CLR', 'SKC', 'FEW', 'SCT', 'BKN', 'OVC']):
                decoded['clouds'] = self.decode_clouds(part)

            # Temperature/dewpoint (e.g., 21/M01)
            elif re.match(r'^(M?\d{2})/(M?\d{2})$', part):
                t, d = part.split('/')
                def to_c(x): return int(x.replace('M', '-'))
                temp_c = to_c(t)
                dew_c = to_c(d)
                temp_f = round((temp_c * 9/5) + 32)
                decoded['temperature'] = f"{temp_c}°C ({temp_f}°F)"
                decoded['dewpoint'] = f"{dew_c}°C"

            # Altimeter (e.g., A3012 -> 30.12 inHg)
            elif part.startswith('A') and len(part) == 5 and part[1:].isdigit():
                alt_inhg = float(part[1:]) / 100
                decoded['pressure'] = f"{alt_inhg:.2f} inHg"

        # Build a short human-readable summary
        summary_parts = []
        for key in ('weather', 'clouds', 'temperature', 'wind', 'visibility', 'pressure'):
            if key in decoded:
                summary_parts.append(decoded[key])
        summary = "; ".join(summary_parts) if summary_parts else "No readable data extracted"
        return {'decoded': decoded, 'summary': summary}


# Flask routes (skeleton)
@app.route('/')
def index():
    return render_template('index.html')


@app.route('/metar', methods=['POST'])
def metar():
    station = request.form.get('station', '').strip().upper()
    if not station:
        return render_template('result.html', error="Please provide a station code.")
    # Fetch METAR from aviationweather.gov (simple example URL)
    url = (
        "https://aviationweather.gov/adds/dataserver_current/httpparam?"
        f"dataSource=metars&requestType=retrieve&format=xml&stationString={station}&hoursBeforeNow=1"
    )
    try:
        resp = requests.get(url, timeout=5)
        resp.raise_for_status()
    except requests.RequestException:
        return render_template('result.html', error="Failed to fetch METAR data.")
    # For this example, assume we retrieved a METAR string
    # In a production app parse XML and extract the raw_text field
    raw_metar = "KHPN 051953Z 36008KT 10SM CLR 21/M01 A3012"
    decoder = METARDecoder()
    result = decoder.decode_metar(raw_metar)
    return render_template('result.html', metar=raw_metar, summary=result['summary'])

Quick smoke test from the command line

A simple one-liner can sanity-check the METARDecoder without running the Flask app.
# Activate your venv and run a quick python one-liner to sanity-check the decoder
source venv/bin/activate && python - <<'PY'
from app import METARDecoder
decoder = METARDecoder()
test_metar = 'KHPN 051953Z 36008KT 10SM CLR 21/M01 A3012'
result = decoder.decode_metar(test_metar)
print("METAR decoding test passed")
print("Summary:", result['summary'])
PY

Committing and commit message guidance

Stage your files:
git add app.py requirements.txt static/ templates/ README.md
A concise, descriptive commit message for the initial release:
git commit -m "Add Flask METAR Reader web application

- Fetches METAR data and decodes to human-readable summaries
- Includes temperature conversion, wind direction mapping, visibility and cloud decoding
- Add initial templates and styles, and README with installation instructions"
When using AI tools to generate or assist with commits, review commit authorship and the content of commit messages. You may choose whether to keep an explicit “Co-Authored-By” line for transparency.
Push to GitHub when ready:
git push origin main
After pushing, verify the repository page on GitHub and add screenshots, badges, or additional docs (CONTRIBUTING.md, CODE_OF_CONDUCT.md) to encourage contributors.
A screenshot of a dark-themed desktop showing a browser open to a GitHub repository README for a "METAR Reader" web app, with the file list and project description visible. A code editor/IDE is open in the background.

Repository structure (example)

PathPurpose
app.pyMain Flask application with METAR decoder
requirements.txtPython dependencies
templates/HTML templates (index.html, result.html)
static/Static assets (style.css, images)
README.mdProject overview, installation, usage

Installation and quick start

git clone https://github.com/yourusername/KodeKloud-METAR-Reader.git
cd KodeKloud-METAR-Reader

python3 -m venv venv
source venv/bin/activate   # On Windows: venv\Scripts\activate
pip install -r requirements.txt

export FLASK_APP=app.py
flask run
# Open http://127.0.0.1:5000
If you want to show a table of example airports, usage notes, or screenshots, add them to README.md. You can also provide a CONTRIBUTING.md to guide other developers.
A dark-mode screenshot of a GitHub README for a METAR reader, showing a table of airports and codes at the top and a "How It Works" section explaining the METAR decoding process.

Useful METAR token mappings (quick reference)

TokenMeaningExample
RARain-RA light rain
SNSnow+SN heavy snow
FGFogFG fog present
BRMistBR mist
TSThunderstormTS thunderstorms
FEW/SCT/BKN/OVCCloud coverageBKN100 broken at 10,000 ft
SMStatute miles (visibility)10SM -> 10 miles
AxxxxAltimeter (inHg)A3012 -> 30.12 inHg
M prefixNegative temperaturesM01 = -1°C

Final checklist before public release

  • Update .gitignore to exclude venv, editor files, and secrets
  • Add LICENSE (e.g., MIT)
  • Ensure README contains install, usage, and contribution instructions
  • Add basic tests or a smoke test for the decoder
  • Remove any hard-coded API keys or secrets from the repo
  • Verify commit messages and authorship are as you intend
With these steps the Flask METAR Reader is documented, tested at a basic level, and ready for a GitHub release so users and contributors can easily install, run, and extend the project.

Watch Video