import os from flask import Flask, jsonify, render_template, request from sqlalchemy import func from models import Sensor, SensorReadout, db def create_app(): app = Flask(__name__) db_path = os.path.join(os.path.dirname(__file__), "data.db") app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False db.init_app(app) @app.route("/sensors") def sensors_page(): return render_template("sensors.html") @app.route("/api/docs") def api_docs(): return render_template("docs.html") @app.route("/api/sensors") def api_sensors(): sensors = Sensor.query.order_by(Sensor.name).all() return jsonify([{"id": s.id, "name": s.name} for s in sensors]) @app.route("/api/sensors//types") def api_sensor_types(sensor_id): types = ( db.session.query(SensorReadout.type) .filter_by(sensor_id=sensor_id) .distinct() .order_by(SensorReadout.type) .all() ) return jsonify([t[0] for t in types]) @app.route("/api/sensors//readouts") def api_sensor_readouts(sensor_id): type_ = request.args.get("type") query = SensorReadout.query.filter_by(sensor_id=sensor_id) if type_: query = query.filter_by(type=type_) readouts = query.order_by(SensorReadout.timestamp.desc()).limit(300).all() return jsonify( [ {"value": r.value, "timestamp": r.timestamp.isoformat()} for r in readouts ] ) @app.route("/api/sensors//readouts/hourly") def api_sensor_readouts_hourly(sensor_id): type_ = request.args.get("type") query = ( db.session.query( func.strftime("%Y-%m-%dT%H:00:00", SensorReadout.timestamp).label("hour"), func.avg(SensorReadout.value).label("avg_value"), ) .filter(SensorReadout.sensor_id == sensor_id) ) if type_: query = query.filter(SensorReadout.type == type_) rows = ( query.group_by("hour") .order_by("hour") .all() ) return jsonify( [{"timestamp": r.hour, "value": round(r.avg_value, 2)} for r in rows] ) @app.route("/api/sensors", methods=["POST"]) def api_create_sensor(): data = request.get_json(silent=True) if not data or "name" not in data: return jsonify({"error": "name is required"}), 400 name = data["name"].strip() if not name: return jsonify({"error": "name cannot be empty"}), 400 if Sensor.query.filter_by(name=name).first(): return jsonify({"error": "sensor already exists"}), 409 sensor = Sensor(name=name) db.session.add(sensor) db.session.commit() return jsonify({"id": sensor.id, "name": sensor.name}), 201 @app.route("/api/sensors//readouts", methods=["POST"]) def api_create_readout(sensor_id): sensor = db.session.get(Sensor, sensor_id) if not sensor: return jsonify({"error": "sensor not found"}), 404 data = request.get_json(silent=True) if not data: return jsonify({"error": "request body is required"}), 400 missing = [f for f in ("type", "value") if f not in data] if missing: return jsonify({"error": f"missing fields: {', '.join(missing)}"}), 400 try: value = float(data["value"]) except (TypeError, ValueError): return jsonify({"error": "value must be a number"}), 400 readout = SensorReadout( sensor_id=sensor_id, type=str(data["type"]).strip(), value=value, ) db.session.add(readout) db.session.commit() return jsonify( { "id": readout.id, "sensor_id": readout.sensor_id, "type": readout.type, "value": readout.value, "timestamp": readout.timestamp.isoformat(), } ), 201 return app if __name__ == "__main__": app = create_app() app.run(debug=True, host='0.0.0.0')