# app.py import streamlit as st import json import os import requests import re import time from datetime import datetime from ddgs import DDGS import isbnlib from dotenv import load_dotenv from requests.exceptions import HTTPError from huggingface_hub import InferenceClient import languages # استيراد وحدة الترجمة load_dotenv() # استيراد الملفات المحلية try: from nlm_classifier import AdvancedNLMClassifier from metadata_fetcher import EnhancedMetadataFetcher from cover_fetcher import EnhancedCoverFetcher from marc_generator import ( build_marc_record, generate_marc_iso2709, generate_marcxml ) except ImportError as e: st.error(f"Error importing modules: {e}") # واجهات افتراضية في حال فشل التحميل class AdvancedNLMClassifier: def classify_with_confidence(self, title, summary="", categories=None): return {'nlm_code': 'W 1', 'confidence_level': 'Low'} class EnhancedMetadataFetcher: def fetch_metadata(self, isbn): return {'title': 'Unknown', 'published_date': '', 'publisher': ''} class EnhancedCoverFetcher: def get_cover(self, isbn, title=None): return {'url': f"https://placehold.co/400x600?text=ISBN:{isbn}", 'status': 'fallback'} def build_marc_record(data, isbn): return ["001 ## $a ISBN" + isbn] def generate_marc_iso2709(marc_fields, isbn): return {'iso2709': '', 'human_readable': '', 'fields_count': 0, 'record_length': 0} def generate_marcxml(marc_fields, isbn): return "" # --- 1. SETUP & AUTHENTICATION --- hf_token = os.getenv("HF_TOKEN") USE_AI = os.getenv("USE_AI", "true").lower() == "true" st.set_page_config( page_title="Medical AI Librarian", layout="wide", page_icon="🏥", initial_sidebar_state="collapsed" ) # تهيئة حالة اللغة if 'language' not in st.session_state: st.session_state.language = 'en' # افتراضي إنجليزي lang = st.session_state.language direction = languages.get_language_direction(lang) # تطبيق اتجاه الصفحة عبر CSS مع تصحيح الأقواس st.markdown(f""" """, unsafe_allow_html=True) # تهيئة عميل Hugging Face بنموذج مجاني if hf_token and USE_AI: try: client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.2", token=hf_token) st.sidebar.success(languages.get_text("ai_connected", lang)) except Exception as e: st.sidebar.warning(languages.get_text("ai_error", lang).format(str(e))) client = None else: if not hf_token: st.sidebar.info(languages.get_text("no_hf_token", lang)) client = None # --- 2. CSS STYLING (باقي الأنماط) --- st.markdown(f""" """, unsafe_allow_html=True) # --- 3. FUNCTIONS --- def clean_isbn(isbn_str): """تنظيف وتصحيح ISBN""" cleaned = re.sub(r'[^0-9X]', '', isbn_str.upper()) try: if len(cleaned) == 10: if not isbnlib.is_isbn10(cleaned): return None elif len(cleaned) == 13: if not isbnlib.is_isbn13(cleaned): return None else: return None except: pass return cleaned def search_web_context(isbn, title=None): """بحث في الويب باستخدام ddgs""" queries = [] if title: queries.extend([ f"{title} medical book publication date edition", f"{title} review summary table of contents", f"{title} medical textbook target audience" ]) queries.extend([ f"ISBN {isbn} publication details", f"{isbn} medical book specifications", f"medical library catalog {isbn}" ]) all_results = [] try: with DDGS() as ddgs: for query in queries[:4]: results = list(ddgs.text(query, max_results=3)) for result in results: all_results.append({ 'title': result.get('title', ''), 'snippet': result.get('body', ''), 'url': result.get('href', '') }) except Exception as e: st.sidebar.warning(f"Web search limited: {str(e)}") return all_results if all_results else [] def enhanced_ai_librarian_analysis(isbn, meta_data, web_context): """تحليل محسن مع دقة عالية""" lang = st.session_state.language # استخدام اللغة الحالية if not client or not USE_AI: st.info(languages.get_text("local_classification_mode", lang)) return fallback_local_classification(isbn, meta_data, web_context, lang) metadata_fetcher = EnhancedMetadataFetcher() enhanced_meta = metadata_fetcher.fetch_metadata(isbn) api_title = enhanced_meta.get('title', '') summary_context = enhanced_meta.get('description', '') categories = enhanced_meta.get('categories', []) web_context_text = " ".join([r.get('snippet', '') for r in web_context[:3]]) nlm_classifier = AdvancedNLMClassifier() nlm_result = nlm_classifier.classify_with_confidence( api_title, f"{summary_context} {web_context_text}", categories ) system_prompt = """You are a Senior Medical Cataloging Expert with specialization in NLM classification. IMPORTANT GUIDELINES: 1. Analyze the book's PRIMARY subject focus, not secondary topics 2. Consider the intended audience and purpose 3. For medical textbooks, use W 18-W 20 range 4. For clinical guides, use appropriate WB-WZ codes 5. For basic sciences, use QS-QZ series 6. Provide specific, not generic, classifications 7. Include detailed reasoning for your choice ALWAYS verify the classification matches the book's main content.""" user_prompt = f"""Please provide precise cataloging information for this medical book: ISBN: {isbn} Title: {api_title} Publication Year: {enhanced_meta.get('published_date', 'Unknown')} Publisher: {enhanced_meta.get('publisher', 'Unknown')} Subjects/Categories: {', '.join(categories) if categories else 'None'} Description: {summary_context[:300]} Additional Context: {web_context_text[:300]} NLM Classification Analysis from our system: - Suggested Code: {nlm_result['nlm_code']} - Confidence: {nlm_result['confidence_level']} - Reason: {nlm_result['confidence_reason']} Please provide your professional assessment in this JSON format: {{ "title": "Complete title", "sub_title": "Subtitle if available", "authors": ["Author list"], "edition": "Edition information", "publisher": "Publisher name", "pub_year": "YYYY (extracted accurately)", "pages": "Number of pages", "isbn_10": "ISBN-10", "isbn_13": "ISBN-13", "summary": "Comprehensive 200-word summary", "contents_note": "Detailed table of contents", "audience_category": "Specific audience description", "audience_reason": "Justification", "mesh_subjects": ["Relevant MeSH terms"], "nlm_class": "YOUR PROFESSIONAL NLM CLASSIFICATION", "nlm_class_reason": "Detailed reasoning based on content analysis", "nlm_class_confidence": "High/Medium/Low", "acquisition_decision": "Highly Recommended/Recommended/Optional/Not Recommended", "acquisition_reason": "Collection development reasoning", "quality_score": "1-10 based on authority and relevance", "data_accuracy": "High/Medium/Low based on available information" }} CRITICAL: The NLM classification must be accurate and specific to the main subject.""" try: messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ] response = client.chat_completion( messages, max_tokens=2500, temperature=0.1, top_p=0.9 ) content = response.choices[0].message.content if "```json" in content: content = content.split("```json")[1].split("```")[0] elif "```" in content: content = content.split("```")[1].split("```")[0] data = json.loads(content.strip()) # دمج نتائج NLM المحلية data['nlm_class'] = nlm_result['nlm_code'] data['nlm_class_ai_reason'] = data.get('nlm_class_reason', '') data['nlm_class_local_reason'] = nlm_result['confidence_reason'] data['nlm_class_confidence'] = nlm_result['confidence_level'] data['nlm_class_score'] = nlm_result['confidence_score'] data['nlm_alternatives'] = nlm_result.get('alternative_codes', []) data['metadata_source'] = enhanced_meta.get('source', 'Multiple') data['metadata_confidence'] = enhanced_meta.get('confidence', 'Unknown') if enhanced_meta.get('published_date'): data['pub_year'] = enhanced_meta['published_date'] data['pub_year_source'] = enhanced_meta.get('source', 'API') data['pub_year_confidence'] = enhanced_meta.get('confidence', 'Unknown') if not isinstance(data.get('authors'), list): if enhanced_meta and enhanced_meta.get('authors'): data['authors'] = enhanced_meta['authors'] else: data['authors'] = [] data['illustrations_note'] = 'illustrations (chiefly color), portraits' data['series'] = 'Medical education series' if 'textbook' in data.get('title', '').lower() else '' data['institution'] = enhanced_meta.get('institution', '') if not data.get('pages') and enhanced_meta.get('page_count'): page_count = enhanced_meta['page_count'] if isinstance(page_count, int): if page_count > 100: data['pages'] = f"xvi, {page_count}" else: data['pages'] = str(page_count) return data except HTTPError as e: if e.response.status_code == 402: st.warning(languages.get_text("ai_payment_warning", lang)) else: st.error(languages.get_text("ai_unexpected_error", lang).format(e)) return fallback_local_classification(isbn, enhanced_meta, web_context, lang) except Exception as e: st.error(languages.get_text("ai_unexpected_error", lang).format(str(e))) return fallback_local_classification(isbn, enhanced_meta, web_context, lang) def fallback_local_classification(isbn, enhanced_meta, web_context, lang='en'): """تصنيف محلي متقدم مع بيانات افتراضية ذكية وقابلية للترجمة""" nlm_classifier = AdvancedNLMClassifier() nlm_result = nlm_classifier.classify_with_confidence( enhanced_meta.get('title', ''), enhanced_meta.get('description', ''), enhanced_meta.get('categories', []) ) title = enhanced_meta.get('title', 'Unknown Title') description = enhanced_meta.get('description', '') authors = enhanced_meta.get('authors', []) publisher = enhanced_meta.get('publisher', 'Unknown') pub_year = enhanced_meta.get('published_date', '') # توليد ملخص ذكي (يبقى بالإنجليزية) if description and len(description) > 20: summary = description[:500] + ('...' if len(description) > 500 else '') else: summary = f"This book, '{title}', is a medical publication focusing on {nlm_result['nlm_name']}. " \ f"It is intended for healthcare professionals and students. " \ f"Published by {publisher} in {pub_year if pub_year else 'unknown year'}. " \ f"The work covers key concepts in {nlm_result['nlm_description'].lower() if nlm_result['nlm_description'] else 'medicine'}." # توليد محتويات افتراضية بناءً على التصنيف (تبقى بالإنجليزية) main_topic = nlm_result.get('nlm_name', 'Medicine') contents_templates = { 'Textbook': [ "1. Introduction to the field", "2. Fundamental principles", "3. Clinical applications", "4. Diagnostic approaches", "5. Therapeutic interventions", "6. Case studies", "7. Emerging trends", "8. Review questions" ], 'Surgery': [ "1. Preoperative assessment", "2. Surgical anatomy", "3. Operative techniques", "4. Postoperative care", "5. Complications and management", "6. Minimally invasive surgery", "7. Surgical outcomes" ], 'Cardiology': [ "1. Cardiac anatomy and physiology", "2. Diagnostic imaging", "3. Ischemic heart disease", "4. Heart failure", "5. Arrhythmias", "6. Valvular disorders", "7. Pharmacotherapy", "8. Interventional cardiology" ], 'Neurology': [ "1. Neuroanatomy", "2. Neurological examination", "3. Stroke and cerebrovascular disease", "4. Epilepsy", "5. Neurodegenerative disorders", "6. Headache and pain", "7. Neuromuscular disorders" ], 'Pediatrics': [ "1. Growth and development", "2. Neonatal care", "3. Pediatric infectious diseases", "4. Childhood immunizations", "5. Pediatric emergencies", "6. Adolescent medicine" ] } contents_note = [] for key, template in contents_templates.items(): if key.lower() in main_topic.lower(): contents_note = template break if not contents_note: contents_note = [ "1. Introduction", "2. Core concepts", "3. Clinical relevance", "4. Diagnostic methods", "5. Treatment strategies", "6. Patient management", "7. Future directions", "8. Review and self-assessment" ] # توليد موضوعات MeSH مع ترجمة إذا وجدت mesh_mapping = { 'W 18': [ languages.get_text("mesh_education_medical", lang), languages.get_text("mesh_textbooks", lang), languages.get_text("mesh_curriculum", lang) ], 'WB 100': [ languages.get_text("mesh_clinical_medicine", lang), languages.get_text("mesh_diagnosis", lang), languages.get_text("mesh_therapeutics", lang) ], 'WB 105': [ languages.get_text("mesh_emergency_medicine", lang), languages.get_text("mesh_traumatology", lang), languages.get_text("mesh_critical_care", lang) ], 'WO 100': [ languages.get_text("mesh_general_surgery", lang), languages.get_text("mesh_surgical_procedures", lang) ], 'WS 1': [ languages.get_text("mesh_pediatrics", lang), languages.get_text("mesh_child_development", lang), languages.get_text("mesh_adolescent_medicine", lang) ], 'WG': [ languages.get_text("mesh_cardiology", lang), languages.get_text("mesh_cardiovascular_diseases", lang), languages.get_text("mesh_heart_diseases", lang) ], 'WL': [ languages.get_text("mesh_neurology", lang), languages.get_text("mesh_nervous_system_diseases", lang), languages.get_text("mesh_brain", lang) ], 'QS 1': [ languages.get_text("mesh_anatomy", lang), languages.get_text("mesh_dissection", lang), languages.get_text("mesh_embryology", lang) ], 'QV 1': [ languages.get_text("mesh_pharmacology", lang), languages.get_text("mesh_pharmaceutical_preparations", lang), languages.get_text("mesh_drug_therapy", lang) ], 'QZ 4': [ languages.get_text("mesh_pathology", lang), languages.get_text("mesh_disease", lang), languages.get_text("mesh_clinical_pathology", lang) ], 'WY 100': [ languages.get_text("mesh_nursing_care", lang), languages.get_text("mesh_nursing_process", lang), languages.get_text("mesh_clinical_nursing_research", lang) ], 'WA 1': [ languages.get_text("mesh_public_health", lang), languages.get_text("mesh_preventive_medicine", lang), languages.get_text("mesh_epidemiology", lang) ], 'WM 1': [ languages.get_text("mesh_psychiatry", lang), languages.get_text("mesh_mental_disorders", lang), languages.get_text("mesh_psychotherapy", lang) ] } nlm_code = nlm_result['nlm_code'] mesh_subjects = [] for code_prefix, subjects in mesh_mapping.items(): if nlm_code.startswith(code_prefix) or code_prefix in nlm_code: mesh_subjects = subjects break if not mesh_subjects: mesh_subjects = [ languages.get_text("mesh_medicine", lang), languages.get_text("mesh_medical_sciences", lang), languages.get_text("mesh_health_occupations", lang) ] # تحديد الجمهور المستهدف مع الترجمة if 'Textbook' in nlm_result.get('nlm_name', ''): audience_category = languages.get_text("audience_undergraduate", lang) audience_reason = languages.get_text("audience_reason_textbook", lang) elif 'Education' in nlm_result.get('nlm_name', ''): audience_category = languages.get_text("audience_educators", lang) audience_reason = languages.get_text("audience_reason_education", lang) elif 'Surgery' in nlm_result.get('nlm_name', ''): audience_category = languages.get_text("audience_surgeons", lang) audience_reason = languages.get_text("audience_reason_surgery", lang) else: audience_category = languages.get_text("audience_medical_students", lang) audience_reason = languages.get_text("audience_reason_default", lang) # قرار الشراء مع الترجمة score = nlm_result['confidence_score'] if score >= 8: acquisition_decision = languages.get_text("decision_highly_recommended", lang) acquisition_reason = languages.get_text("acquisition_reason_high", lang) elif score >= 4: acquisition_decision = languages.get_text("decision_recommended", lang) acquisition_reason = languages.get_text("acquisition_reason_medium", lang) else: acquisition_decision = languages.get_text("decision_review_required", lang) acquisition_reason = languages.get_text("acquisition_reason_low", lang) return { 'title': title, 'sub_title': '', 'authors': authors, 'edition': enhanced_meta.get('edition', ''), 'publisher': publisher, 'pub_year': pub_year, 'pages': enhanced_meta.get('page_count', 'xii, 500'), 'isbn_10': enhanced_meta.get('isbn_10', ''), 'isbn_13': enhanced_meta.get('isbn_13', isbn), 'summary': summary, 'contents_note': contents_note, 'audience_category': audience_category, 'audience_reason': audience_reason, 'mesh_subjects': mesh_subjects, 'nlm_class': nlm_code, 'nlm_class_reason': f"Local classification: {nlm_result['confidence_reason']}", 'nlm_class_confidence': nlm_result['confidence_level'], 'nlm_class_score': score, 'acquisition_decision': acquisition_decision, 'acquisition_reason': acquisition_reason, 'quality_score': score // 2 if score > 0 else 5, 'data_accuracy': 'Medium', 'illustrations_note': 'illustrations', 'metadata_source': enhanced_meta.get('source', 'Local'), 'metadata_confidence': enhanced_meta.get('confidence', 'Medium') } # --- 4. MAIN UI --- if 'analysis_data' not in st.session_state: st.session_state.analysis_data = None if 'search_history' not in st.session_state: st.session_state.search_history = [] # --- HEADER --- st.markdown(f"""