# IndiDermaX — Android Integration Guide ## Quick Start ``` Base URL: https://avishek8136-indidermax.hf.space No auth keys needed — HF Space is public. Warm-up: call GET /api/health first (cold start takes 20-30s). ``` ### Test with curl before writing Android code ```bash curl https://avishek8136-indidermax.hf.space/api/health ``` --- ## ⚠️ Cold Start Warning HuggingFace Spaces sleep after ~15 minutes of inactivity. The first request after sleep **takes 20-30 seconds** while the container starts. **Fix:** Ping `/api/health` in `Application.onCreate()` as a warm-up. Show a spinner until it responds. ```kotlin class IndiDermaXApp : Application() { override fun onCreate() { super.onCreate() CoroutineScope(Dispatchers.IO).launch { try { val response = api.healthCheck() Log.i("IndiDermaX", "Warmed up: mode=${response.mode}") } catch (e: Exception) { Log.w("IndiDermaX", "Warm-up failed: ${e.message}") } } } } ``` Set Android network timeout to **60 seconds** (OkHttp/Retrofit default is 10s — too short). --- ## API Endpoints ### 1. Health Check ``` GET /api/health ``` **Response:** ```json { "status": "healthy", "mode": "neo4j_live", "neo4j": true, "nvidia": true, "timestamp": "2026-05-11T00:00:00Z" } ``` `mode` values: `"neo4j_live"` = full production | `"cache_fallback"` = Neo4j down, using local cache | `"kb_only"` = degraded --- ### 2. Diagnose (JSON — recommended for Android) Send text symptoms + optional base64 image. ``` POST /api/diagnose Content-Type: application/json ``` **Request:** ```json { "message": "Red scaly ring-shaped patch on my arm, very itchy, spreading for 2 weeks", "patient_age": 25, "image_base64": "/9j/4AAQSkZJRg...", "session_id": "android_session_001" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `message` | string | No | Symptom description (free text) | | `patient_age` | int | No | Age (or auto-extracted from message) | | `image_base64` | string | No | Base64-encoded JPEG (no data URI prefix) | | `session_id` | string | No | For tracking — use device ID | **Response:** ```json { "success": true, "top_disease": "Tinea Corporis", "top_score": 18.45, "candidates": [ {"disease": "Tinea Corporis", "score": 18.45}, {"disease": "Psoriasis", "score": 15.20}, {"disease": "Eczema", "score": 12.80}, {"disease": "Contact Dermatitis", "score": 10.50}, {"disease": "Tinea Cruris", "score": 9.30} ], "differentials": [ {"disease": "Psoriasis", "score": 15.20}, {"disease": "Eczema", "score": 12.80}, {"disease": "Contact Dermatitis", "score": 10.50} ], "evidence": [ {"title": "Clinical: annular ring-shaped erythematous-plaque central-clearing", "source": "CLINICAL_KB"}, {"title": "Dermatology reference: tinea corporis", "source": "PubMed"} ], "answer": "## 🏥 Diagnosis: **Tinea Corporis**\n\n...", "log_text": "[0.0s] 1_input/parser: ...\n...", "pipeline": { "stages": 6, "agents": 5, "neo4j": true, "vision": true } } ``` ### 3. Diagnose (Multipart Upload) Easier for Android camera/gallery images — no base64 encoding needed. ``` POST /api/diagnose/upload Content-Type: multipart/form-data ``` **Form Fields:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `message` | string | No | Symptom description | | `patient_age` | int | No | Patient age | | `image` | file | No | JPEG/PNG image file | **Response:** Same JSON structure as `/api/diagnose`. ### 4. Chat (Multi-turn Conversation) ``` POST /api/chat Content-Type: application/json ``` **Request:** ```json { "message": "It's very itchy and spreading to other areas", "session_id": "android_session_001", "image_base64": null, "patient_age": 25, "history": [] } ``` **Response:** ```json { "response": "## 🏥 Diagnosis: **Tinea Corporis**\n...", "session_id": "android_session_001", "top_disease": "Tinea Corporis", "top_score": 20.30, "follow_up_question": "Have you tried any antifungal treatments?" } ``` When `top_score < 3.0`, the API includes a `follow_up_question` — show this to the user as a prompt. --- ## Kotlin Integration ### Retrofit Setup ```kotlin // build.gradle.kts // implementation("com.squareup.retrofit2:retrofit:2.11.0") // implementation("com.squareup.retrofit2:converter-gson:2.11.0") // implementation("com.squareup.okhttp3:okhttp:4.12.0") // implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") val okHttp = OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build() val retrofit = Retrofit.Builder() .baseUrl("https://avishek8136-indidermax.hf.space/") .client(okHttp) .addConverterFactory(GsonConverterFactory.create()) .build() val api = retrofit.create(IndiDermaXApi::class.java) ``` ### Retrofit Interface ```kotlin interface IndiDermaXApi { @GET("api/health") suspend fun healthCheck(): HealthResponse @POST("api/diagnose") suspend fun diagnose(@Body request: DiagnoseRequest): DiagnoseResponse @Multipart @POST("api/diagnose/upload") suspend fun diagnoseUpload( @Part("message") message: RequestBody, @Part("patient_age") age: RequestBody, @Part image: MultipartBody.Part ): DiagnoseResponse @POST("api/chat") suspend fun chat(@Body request: ChatRequest): ChatResponse } ``` ### Data Classes ```kotlin data class HealthResponse( val status: String, val mode: String, // "neo4j_live" | "cache_fallback" | "kb_only" val neo4j: Boolean, val nvidia: Boolean, val timestamp: String ) data class DiagnoseRequest( val message: String = "", val patient_age: Int? = null, val image_base64: String? = null, val session_id: String = "android" ) data class DiagnoseResponse( val success: Boolean, val top_disease: String, val top_score: Double, val candidates: List, val differentials: List, val evidence: List, val answer: String, // Markdown — render in a WebView or parse val log_text: String, val pipeline: PipelineInfo ) data class Candidate( val disease: String, val score: Double ) data class EvidenceItem( val title: String, val source: String ) data class PipelineInfo( val stages: Int, val agents: Int, val neo4j: Boolean, val vision: Boolean ) data class ChatRequest( val message: String, val session_id: String = "chat", val image_base64: String? = null, val patient_age: Int? = null, val history: List = emptyList() ) data class ChatMessage( val role: String, // "user" | "assistant" val content: String ) data class ChatResponse( val response: String, val session_id: String, val top_disease: String, val top_score: Double, val follow_up_question: String ) ``` ### Image to Base64 ```kotlin fun Bitmap.toBase64Diagnose(): String { val stream = ByteArrayOutputStream() this.compress(Bitmap.CompressFormat.JPEG, 85, stream) return Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP) } fun File.toBase64Diagnose(): String { return Base64.encodeToString(this.readBytes(), Base64.NO_WRAP) } ``` ### Usage Example (ViewModel) ```kotlin class DiagnoseViewModel(private val api: IndiDermaXApi) : ViewModel() { private val _result = MutableLiveData() val result: LiveData = _result private val _loading = MutableLiveData(false) val loading: LiveData = _loading fun diagnose(text: String, age: Int?, imageBase64: String?) { viewModelScope.launch { _loading.value = true try { val response = api.diagnose( DiagnoseRequest( message = text, patient_age = age, image_base64 = imageBase64, session_id = UUID.randomUUID().toString() ) ) _result.value = response } catch (e: Exception) { // Handle network error — show retry button Log.e("Diagnose", "Failed", e) } finally { _loading.value = false } } } } ``` --- ## Error Handling | HTTP Code | Meaning | Body | |-----------|---------|------| | `200` | Success | Normal response JSON | | `400` | Bad request (invalid base64, missing fields) | `{"success":false,"error":"..."}` | | `422` | Validation error | `{"detail":[{"loc":[...],"msg":"..."}]}` | | `500` | Server error | `{"success":false,"error":"...","endpoint":"..."}` | | `504` | Timeout (>45s) | `{"success":false,"error":"Request timed out..."}` | ### Error Parsing (Retrofit) ```kotlin // In your ViewModel or repository: try { val response = api.diagnose(request) if (response.success) { // Show diagnosis } else { // response.success == false — show error message } } catch (e: HttpException) { when (e.code()) { 400, 422 -> showError("Invalid input. Check your message and image.") 500 -> showError("Server error. Please try again.") 504 -> showError("Request timed out. The image may be too large. Try again.") else -> showError("Network error: ${e.message}") } } catch (e: IOException) { showError("No internet connection. Check your network.") } ``` --- ## Pipeline Architecture ``` Android App │ ├── POST /api/diagnose ────► FastAPI Server (port 7860) │ (text + base64 image) │ │ ├── Stage 1: Input Processing │ ├── Stage 2: Feature Extraction (text) │ ├── Stage 2b: NVIDIA NIM Vision (image → descriptors + body location) │ ├── Stage 3: Neo4j Graph Candidate Retrieval │ ├── Stage 4a: Visual Concept Agent │ ├── Stage 4b: Symptom Analyst │ ├── Stage 4c: Temporal Pattern Matcher (DTW) │ ├── Stage 4d: Differential Diagnosis Debater │ ├── Stage 4e: Evidence Synthesizer │ └── Stage 5: Final Output │ └── Response ◄──────────────── JSON { success, top_disease, top_score, candidates[5], differentials[3], evidence[5], answer, log_text, pipeline } ``` --- ## Testing with curl (before writing Android code) ```bash # 1. Warm up (do this first — cold start takes 20-30s) curl -s https://avishek8136-indidermax.hf.space/api/health | python3 -m json.tool # 2. Text-only diagnosis curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose \ -H "Content-Type: application/json" \ -d '{"message":"red scaly ring-shaped patch on arm, very itchy for 2 weeks","patient_age":25}' \ | python3 -m json.tool # 3. Diagnosis with image (multipart) curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose/upload \ -F "message=ring shaped rash on arm, itchy" \ -F "patient_age=30" \ -F "image=@/path/to/skin_image.jpg" \ | python3 -m json.tool # 4. Diagnosis with image (base64) IMG_BASE64=$(base64 -w0 /path/to/skin_image.jpg) curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose \ -H "Content-Type: application/json" \ -d "{\"message\":\"itchy red patch\",\"image_base64\":\"$IMG_BASE64\",\"patient_age\":25}" \ | python3 -m json.tool # 5. Chat (multi-turn) curl -s -X POST https://avishek8136-indidermax.hf.space/api/chat \ -H "Content-Type: application/json" \ -d '{"message":"it is spreading and very painful now","session_id":"test_001","patient_age":25}' \ | python3 -m json.tool ``` --- ## Disclaimer ⚠️ **AI-assisted decision support tool for educational purposes only.** Always consult a qualified dermatologist for in-person examination and diagnosis. This app does not replace professional medical advice.