From 2e51686c13773fd24c9551d0e3549e3325a1c1a9 Mon Sep 17 00:00:00 2001 From: livindra Date: Tue, 28 Apr 2026 22:08:16 +0700 Subject: [PATCH] last --- app.py | 63 +++++++++---------------- expert_system.py | 17 ++----- utils/mysql_db.py | 118 +++++----------------------------------------- 3 files changed, 39 insertions(+), 159 deletions(-) diff --git a/app.py b/app.py index bf676de..e379645 100644 --- a/app.py +++ b/app.py @@ -8,9 +8,8 @@ import uuid import time from dotenv import load_dotenv -# Load environment variables from .env file only for local development. -if not os.environ.get('PORT') and not os.environ.get('RAILWAY_ENVIRONMENT') and not os.environ.get('RAILWAY_PROJECT_ID'): - load_dotenv() +# Load environment variables from .env file +load_dotenv() from utils.helpers import load_model, estimate_prediction_confidence from utils.preprocessing import preprocess_image, preprocess_pipeline, validate_cattle_image @@ -40,34 +39,6 @@ def get_symptom_desc(symptom_code): return kb.gejala.get(symptom_code, symptom_code) -def _serialize_knowledge_base(): - """Return the current expert knowledge in JSON-serializable form.""" - return { - 'source': 'mysql' if MYSQL_AVAILABLE else 'builtin', - 'gejala': dict(expert_system.get_gejala_list()), - 'gejala_groups': dict(expert_system.get_gejala_groups()), - 'penyakit': dict(expert_system.kb.penyakit), - 'aturan': list(expert_system.kb.aturan), - } - - -@app.route('/api/expert-knowledge') -def api_expert_knowledge(): - """Expose PMK expert knowledge as JSON.""" - return jsonify(_serialize_knowledge_base()) - - -@app.route('/api/gejala') -def api_gejala(): - """Expose the PMK gejala master list as JSON.""" - payload = _serialize_knowledge_base() - return jsonify({ - 'source': payload['source'], - 'gejala': payload['gejala'], - 'gejala_groups': payload['gejala_groups'], - }) - - try: from utils.mysql_db import ( save_prediction_mysql, @@ -80,16 +51,26 @@ try: get_diagnosis_by_prediction_id, get_engine, ) - engine = get_engine() - MYSQL_AVAILABLE = engine is not None - if MYSQL_AVAILABLE: - try: - init_mysql_tables() - print("✓ MySQL database initialized") - except Exception as init_err: - print(f"⚠️ Warning: Database tables initialization failed: {init_err}") - else: - print("[APP] MySQL disabled: no valid database configuration found") + + try: + engine = get_engine() + # quick connect test + with engine.connect() as conn: + # initialize tables if needed + try: + init_mysql_tables() + except Exception as init_err: + + print(f"⚠️ Warning: Database tables initialization failed: {init_err}") + print("✓ MySQL Database connected successfully") + MYSQL_AVAILABLE = True + except Exception as e: + import traceback + print('❌ MySQL not available at startup:') + print(f" Error: {e}") + print(" Cek: 1) MySQL Server berjalan? 2) .env credentials benar? 3) Database 'deteksi_pmk' ada?") + traceback.print_exc() + MYSQL_AVAILABLE = False except Exception as import_err: print(f"❌ Failed to import MySQL utilities: {import_err}") MYSQL_AVAILABLE = False diff --git a/expert_system.py b/expert_system.py index 5c13fa9..397d2eb 100644 --- a/expert_system.py +++ b/expert_system.py @@ -36,19 +36,10 @@ class KnowledgeBase: ) def _set_fallback_knowledge(self): - try: - from utils.mysql_db import get_builtin_expert_knowledge - - knowledge = get_builtin_expert_knowledge() - self.gejala = knowledge.get('gejala', {}) - self.penyakit = knowledge.get('penyakit', {}) - self.aturan = knowledge.get('aturan', []) - self.gejala_groups = knowledge.get('gejala_groups', self._empty_groups()) - except Exception: - self.gejala = {} - self.penyakit = {} - self.aturan = [] - self.gejala_groups = self._empty_groups() + self.gejala = {} + self.penyakit = {} + self.aturan = [] + self.gejala_groups = self._empty_groups() def _load_from_database(self): try: diff --git a/utils/mysql_db.py b/utils/mysql_db.py index 2bef856..9f72e4f 100644 --- a/utils/mysql_db.py +++ b/utils/mysql_db.py @@ -17,37 +17,22 @@ from sqlalchemy.exc import SQLAlchemyError import pytz TZ_INDONESIA = pytz.timezone('Asia/Jakarta') -DATABASE_URL = os.environ.get('DATABASE_URL', '').strip() - - -def _normalize_port(value, default='3306'): - """Return a numeric MySQL port string or a safe default.""" - if value is None: - return default - - value = str(value).strip() - return value if value.isdigit() else default +DATABASE_URL = os.environ.get('DATABASE_URL') def _build_database_uri(): """Build a SQLAlchemy database URI from Railway or local environment variables.""" - db_host = (os.environ.get('DB_HOST') or os.environ.get('MYSQLHOST') or '').strip() - db_user = (os.environ.get('DB_USER') or os.environ.get('MYSQLUSER') or '').strip() + if DATABASE_URL: + url = make_url(DATABASE_URL) + if url.drivername == 'mysql': + url = url.set(drivername='mysql+pymysql') + return str(url) + + db_host = os.environ.get('DB_HOST') or os.environ.get('MYSQLHOST', 'localhost') + db_port = os.environ.get('DB_PORT') or os.environ.get('MYSQLPORT', '3306') + db_user = os.environ.get('DB_USER') or os.environ.get('MYSQLUSER', 'root') db_password = os.environ.get('DB_PASSWORD') or os.environ.get('MYSQLPASSWORD', '') - db_name = (os.environ.get('DB_NAME') or os.environ.get('MYSQLDATABASE') or '').strip() - - if not db_host or not db_user or not db_name: - if DATABASE_URL: - try: - url = make_url(DATABASE_URL) - if url.drivername == 'mysql': - url = url.set(drivername='mysql+pymysql') - return str(url) - except Exception: - return None - return None - - db_port = _normalize_port(os.environ.get('DB_PORT') or os.environ.get('MYSQLPORT')) + db_name = os.environ.get('DB_NAME') or os.environ.get('MYSQLDATABASE', 'deteksi_pmk') if db_password: return f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" @@ -58,17 +43,9 @@ def _build_database_uri(): DATABASE_URI = _build_database_uri() # Create engine -if DATABASE_URI: - try: - engine = create_engine(DATABASE_URI, echo=False, pool_pre_ping=True) - except Exception as e: - print(f"[MYSQL] Database engine unavailable: {e}") - engine = None -else: - engine = None - +engine = create_engine(DATABASE_URI, echo=False, pool_pre_ping=True) Base = declarative_base() -Session = sessionmaker(bind=engine) if engine is not None else sessionmaker() +Session = sessionmaker(bind=engine) def _prediction_source_from_features(features): @@ -292,50 +269,6 @@ DEFAULT_EXPERT_RULES = [ }, ] - -def get_builtin_expert_knowledge(): - """Return expert knowledge from the repository defaults when MySQL is unavailable.""" - gejala = OrderedDict() - penyakit = OrderedDict() - aturan = [] - grouped_codes = {k: [] for k in _GROUP_TITLES.keys()} - - for item in DEFAULT_EXPERT_SYMPTOMS: - gejala[item['code']] = item['description'] - category = (item.get('category') or 'umum').lower() - if category not in grouped_codes: - grouped_codes[category] = [] - grouped_codes[category].append(item['code']) - - for item in DEFAULT_EXPERT_DISEASES: - penyakit[item['code']] = { - 'nama': item['name'], - 'deskripsi': item['description'], - 'solusi': list(item.get('solutions', [])), - } - - for item in DEFAULT_EXPERT_RULES: - aturan.append({ - 'kode': item['code'], - 'gejala': list(item.get('symptom_codes', [])), - 'hasil': item['result_disease_code'], - 'deskripsi': item.get('description', ''), - }) - - gejala_groups = OrderedDict() - for key, title in _GROUP_TITLES.items(): - gejala_groups[key] = { - 'title': title, - 'codes': grouped_codes.get(key, []), - } - - return { - 'gejala': dict(gejala), - 'penyakit': dict(penyakit), - 'aturan': aturan, - 'gejala_groups': gejala_groups, - } - _GROUP_TITLES = OrderedDict([ ('umum', 'Gejala Umum / Sistemik'), ('mulut', 'Gejala Mulut / Oral'), @@ -352,10 +285,6 @@ def get_engine(): def init_mysql_tables(): """Initialize database tables""" - if engine is None: - print("[MYSQL] Skipping table initialization because database engine is unavailable") - return False - try: Base.metadata.create_all(engine) migrate_diagnosis_history_schema() @@ -363,7 +292,6 @@ def init_mysql_tables(): seed_expert_knowledge(force=False) sync_expert_rule_symptom_relations() print("Database tables initialized successfully") - return True except SQLAlchemyError as e: print(f"Error initializing database tables: {e}") raise @@ -371,9 +299,6 @@ def init_mysql_tables(): def migrate_diagnosis_history_schema(): """Remove legacy diagnosis_history columns that are no longer used.""" - if engine is None: - return - try: inspector = inspect(engine) table_names = inspector.get_table_names() @@ -393,9 +318,6 @@ def migrate_diagnosis_history_schema(): def migrate_expert_rule_disease_fk(): """Add the missing foreign key from expert_rules.result_disease_code to expert_diseases.code.""" - if engine is None: - return - try: inspector = inspect(engine) if 'expert_rules' not in inspector.get_table_names() or 'expert_diseases' not in inspector.get_table_names(): @@ -420,9 +342,6 @@ def migrate_expert_rule_disease_fk(): def sync_expert_rule_symptom_relations(): """Backfill the expert_rules_expert_symptoms join table from stored symptom codes.""" - if engine is None: - return - session = Session() try: session.execute(expert_rules_expert_symptoms.delete()) @@ -458,14 +377,6 @@ def sync_expert_rule_symptom_relations(): def seed_expert_knowledge(force=False): """Isi data default gejala, penyakit, dan aturan sistem pakar.""" - if engine is None: - return { - 'seeded': False, - 'symptoms': 0, - 'diseases': 0, - 'rules': 0, - } - session = Session() try: existing_symptoms = session.query(ExpertSymptom).count() @@ -529,9 +440,6 @@ def seed_expert_knowledge(force=False): def get_expert_knowledge_mysql(): """Ambil knowledge base sistem pakar (gejala, penyakit, aturan) dari MySQL.""" - if engine is None: - return None - session = Session() try: symptoms = session.query(ExpertSymptom).order_by(ExpertSymptom.display_order.asc(), ExpertSymptom.code.asc()).all()