{ "info": { "name": "AgriTech AI API", "description": "Complete API collection for the Plant Disease Prediction system.\n\n## Setup\n1. Import this collection into Postman\n2. Set `baseUrl` to `http://localhost:8000` (or your LAN IP)\n3. Run \"1. Login\" \u2014 JWT auto-saves to `token`\n4. Run \"2. Login as Admin\" \u2014 admin JWT saves to `adminToken`\n\n## Admin endpoints\nUse the admin token (requires an is_staff user). Create one:\n`python manage.py createsuperuser`\n\n## Test Flow Order\n1. Login (get user token)\n2. Predict a leaf photo \u2192 check confidence_percent, disease_info\n3. Login as Admin (get admin token)\n4. List all predictions \u2192 find prediction ID\n5. Reclassify Unknown \u2192 assign real class\n6. Verify prediction \u2192 confirm disease created\n7. Search/Get disease detail \u2192 see description, treatment, partners\n8. Trigger retraining \u2192 model learns new class\n\n## New Feature: Suggested Species\nWhen a leaf is \"Unknown\", Model 2 (HuggingFace) identifies the plant species. Check the `suggested_species` field in the response.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "baseUrl", "value": "http://localhost:8000" }, { "key": "token", "value": "" }, { "key": "adminToken", "value": "" }, { "key": "predictionId", "value": "" }, { "key": "adminPredictionId", "value": "" }, { "key": "refreshToken", "value": "" }, { "key": "diseaseId", "value": "" }, { "key": "newDiseaseClass", "value": "" } ], "item": [ { "name": "Auth", "item": [ { "name": "1. Login \u2014 Get JWT Token", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " pm.collectionVariables.set('token', json.access);", " pm.collectionVariables.set('refreshToken', json.refresh);", " console.log('Access token set:', json.access.substring(0, 20) + '...');", "} else {", " console.log('Login failed:', pm.response.code, JSON.stringify(pm.response.json()));", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"testuser\",\n \"password\": \"testpass123\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/token/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "token", "" ] } } }, { "name": "2. Login as Admin \u2014 Get JWT Token", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " pm.collectionVariables.set('adminToken', json.access);", " pm.collectionVariables.set('refreshToken', json.refresh);", " console.log('Admin token set:', json.access.substring(0, 20) + '...');", " pm.test('Admin login successful', function() {", " pm.expect(json.access).to.not.be.empty;", " });", "} else {", " console.log('Admin login failed. Create a superuser with: python manage.py createsuperuser');", " console.log('Response:', pm.response.code, JSON.stringify(pm.response.json()));", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/token/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "token", "" ] } } }, { "name": "3. Signup \u2014 Create Account", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 201) {", " var json = pm.response.json();", " console.log('User created:', json.user.username);", " pm.test('Signup creates user with ID', function() {", " pm.expect(json.user.id).to.be.a('number');", " });", "} else if (pm.response.code === 400) {", " console.log('Signup validation error:', JSON.stringify(pm.response.json()));", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"newuser\",\n \"email\": \"newuser@example.com\",\n \"password\": \"securepass123\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/signup/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "signup", "" ] } } }, { "name": "4. Refresh Token", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " pm.collectionVariables.set('token', json.access);", " console.log('New access token set:', json.access.substring(0, 20) + '...');", " pm.test('Refresh returns new access token', function() {", " pm.expect(json.access).to.not.be.empty;", " });", "} else {", " console.log('Refresh failed:', pm.response.code, JSON.stringify(pm.response.json()));", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"refresh\": \"{{refreshToken}}\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/token/refresh/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "token", "refresh", "" ] } } } ] }, { "name": "Reference Data", "item": [ { "name": "5. List Diseases", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Diseases found:', json.count || json.results.length);", " pm.test('Response has results array', function() {", " pm.expect(json.results).to.be.an('array');", " });", " if (json.results.length > 0) {", " pm.collectionVariables.set('diseaseId', json.results[0].id);", " console.log('First disease ID:', json.results[0].id);", " pm.test('Disease has description', function() {", " pm.expect(json.results[0].description).to.not.be.empty;", " });", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/v1/diseases/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "diseases", "" ] } } }, { "name": "6. Search Diseases", "description": "Search diseases by name. Uses DRF SearchFilter.", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/diseases/?search=apple", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "diseases", "" ], "query": [ { "key": "search", "value": "apple" } ] } } }, { "name": "6b. Get Disease Detail", "description": "Get full disease details including description, treatment (chemical + organic cure, prevention tips) and partner companies. Tests that symptoms and treatment info is available.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Disease:', json.common_name);", " console.log('Description:', json.description.substring(0, 80) + '...');", " pm.test('Disease has description (symptoms info)', function() {", " pm.expect(json.description).to.not.be.empty;", " });", " if (json.treatment) {", " console.log('Chemical cure:', json.treatment.chemical_cure);", " console.log('Organic cure:', json.treatment.organic_cure);", " console.log('Prevention tips:', json.treatment.prevention_tips);", " pm.test('Treatment has chemical cure (medicines)', function() {", " pm.expect(json.treatment.chemical_cure).to.not.be.empty;", " });", " pm.test('Treatment has organic cure', function() {", " pm.expect(json.treatment.organic_cure).to.not.be.empty;", " });", " pm.test('Treatment has prevention tips', function() {", " pm.expect(json.treatment.prevention_tips).to.not.be.empty;", " });", " }", " if (json.partners && json.partners.length > 0) {", " console.log('Partner companies:', json.partners.length);", " pm.test('Partners include company names', function() {", " pm.expect(json.partners[0].name).to.not.be.empty;", " });", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/v1/diseases/{{diseaseId}}/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "diseases", "{{diseaseId}}", "" ] } } }, { "name": "7. List Treatments", "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/v1/treatments/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "treatments", "" ] } } }, { "name": "8. List Companies", "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/v1/companies/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "companies", "" ] } } }, { "name": "11b. Search Disease by Species", "description": "Search diseases by plant species name. Tests that species-linked disease records are searchable in the API.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Results for species search:', json.count || json.results.length);", " pm.test('Search returns results', function() {", " pm.expect(json.results).to.be.an('array');", " });", " if (json.results.length > 0) {", " json.results.forEach(function(d) {", " console.log(' -', d.class_name, '(' + d.plant_type + ')');", " pm.test('Disease ' + d.class_name + ' has plant_type', function() {", " pm.expect(d.plant_type).to.not.be.empty;", " });", " });", " } else {", " console.log('No results for this species. Try a different search term.');", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/diseases/?search=Tomato", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "diseases", "" ], "query": [ { "key": "search", "value": "Tomato" } ] } } } ] }, { "name": "Prediction", "item": [ { "name": "9. Predict Disease (Upload Photo)", "description": "Upload a leaf photo.\n\n- 201 \u2192 success with `data`, `disease_info`\n- 400 \u2192 error (NonPlant, oversized, wrong type)\n- 500 \u2192 inference failure\n\n## Test Results (asserted in test script)\n- `confidence_percent` is always returned as a number\n- `suggested_species` is present when prediction is Unknown\n- `disease_info` includes treatment (chemical_cure, organic_cure, prevention_tips) when prediction matches a known disease\n\n## Percentage Test\nConfidence is stored as a float (0-1) in the DB and returned as `confidence_percent` (0-100) in the response.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 201) {", " var json = pm.response.json();", " var data = json.data;", " pm.collectionVariables.set('predictionId', data.id);", " console.log('Prediction ID:', data.id);", " console.log('Class:', data.predicted_class);", " console.log('Confidence:', data.confidence);", " console.log('Confidence %:', data.confidence_percent + '%');", "", " // TEST: confidence_percent is always returned", " pm.test('confidence_percent is a number', function() {", " pm.expect(data.confidence_percent).to.be.a('number');", " });", " pm.test('confidence_percent = confidence * 100', function() {", " pm.expect(data.confidence_percent).to.equal(data.confidence * 100);", " });", "", " // TEST: suggested_species is present (may be null for known, string for Unknown)", " pm.test('suggested_species field exists', function() {", " pm.expect(data).to.have.property('suggested_species');", " });", " if (data.predicted_class === 'Unknown') {", " console.log('Suggested species:', data.suggested_species);", " pm.test('suggested_species is populated for Unknown', function() {", " pm.expect(data.suggested_species).to.not.be.null;", " });", " }", "", " // TEST: disease_info includes treatment (symptoms, medicines)", " if (json.disease_info) {", " console.log('Disease info found for:', data.predicted_class);", " if (json.disease_info.treatment) {", " console.log('Chemical cure:', json.disease_info.treatment.chemical_cure);", " console.log('Organic cure:', json.disease_info.treatment.organic_cure);", " console.log('Prevention tips:', json.disease_info.treatment.prevention_tips);", " pm.test('disease_info has chemical cure', function() {", " pm.expect(json.disease_info.treatment.chemical_cure).to.not.be.empty;", " });", " pm.test('disease_info has organic cure', function() {", " pm.expect(json.disease_info.treatment.organic_cure).to.not.be.empty;", " });", " pm.test('disease_info has prevention tips', function() {", " pm.expect(json.disease_info.treatment.prevention_tips).to.not.be.empty;", " });", " } else {", " console.log('No treatment record for this disease yet.');", " }", " } else {", " console.log('No disease_info (Unknown or no matching disease record)');", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "body": { "mode": "formdata", "formdata": [ { "key": "image", "type": "file", "src": "" } ] }, "url": { "raw": "{{baseUrl}}/api/v1/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "" ] } } }, { "name": "9b. Predict Unknown & Verify Species", "description": "Upload a leaf photo that will be classified as Unknown. Tests that the HuggingFace Model 2 identifies the plant species and populates `suggested_species`. This validates the two-model pipeline: Model 1 (TF) returns Unknown, Model 2 (HF) returns species name.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 201) {", " var json = pm.response.json();", " var data = json.data;", " pm.collectionVariables.set('predictionId', data.id);", " console.log('Prediction ID:', data.id);", " console.log('Predicted class:', data.predicted_class);", " console.log('Suggested species:', data.suggested_species);", " console.log('Confidence:', data.confidence);", "", " if (data.predicted_class === 'Unknown') {", " pm.test('Unknown prediction has suggested_species', function() {", " pm.expect(data.suggested_species).to.not.be.null;", " pm.expect(data.suggested_species).to.not.be.empty;", " });", " console.log('Model 2 identified species:', data.suggested_species);", " pm.collectionVariables.set('newDiseaseClass', data.suggested_species + '__Healthy');", " console.log('Admin can accept as:', data.suggested_species + '__Healthy');", " } else {", " console.log('Prediction had a match (not Unknown). Try a different leaf photo.');", " }", "", " pm.test('Response includes predicted_class', function() {", " pm.expect(data.predicted_class).to.not.be.empty;", " });", " pm.test('Response includes confidence', function() {", " pm.expect(data.confidence).to.be.a('number');", " });", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "body": { "mode": "formdata", "formdata": [ { "key": "image", "type": "file", "src": "" } ] }, "url": { "raw": "{{baseUrl}}/api/v1/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "" ] } } }, { "name": "9c. Predict Non-Plant Photo (Test Model Validation)", "description": "Upload an image that is not a plant (e.g. car, face, empty wall). The API's built-in `_is_likely_plant` AI function should detect the lack of green edge density and reject it with a 400 Bad Request.\n\nThis tests the first layer of the AI validation pipeline.", "event": [ { "listen": "test", "script": { "exec": [ "pm.test('Response should be 400 Bad Request', function() {", " pm.response.to.have.status(400);", "});", "pm.test('Response should say NonPlant', function() {", " var json = pm.response.json();", " pm.expect(json.predicted_class).to.equal('NonPlant');", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "body": { "mode": "formdata", "formdata": [ { "key": "image", "type": "file", "src": "" } ] }, "url": { "raw": "{{baseUrl}}/api/v1/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "" ] } } }, { "name": "9d. Predict Out-of-Distribution Leaf (Strict Novelty)", "description": "Upload a leaf that is NOT in the 38 classes (e.g. Guava leaf). This tests the new strict 0.65 novelty threshold and 95% confidence threshold.\n\nEven if the TF model tries to confidently guess 'Tomato Early Blight', the AI pipeline should override it and return 'Unknown'.", "event": [ { "listen": "test", "script": { "exec": [ "pm.test('Response should be 201 Created', function() {", " pm.response.to.have.status(201);", "});", "pm.test('Prediction should be Unknown', function() {", " var json = pm.response.json();", " pm.expect(json.data.predicted_class).to.equal('Unknown');", "});", "pm.test('Confidence should be logged correctly', function() {", " var json = pm.response.json();", " pm.expect(json.data.confidence_percent).to.be.a('number');", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "body": { "mode": "formdata", "formdata": [ { "key": "image", "type": "file", "src": "" } ] }, "url": { "raw": "{{baseUrl}}/api/v1/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "" ] } } }, { "name": "10. List My Predictions", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('My predictions:', json.count || json.results.length);", " pm.test('My predictions returns array', function() {", " pm.expect(json.results).to.be.an('array');", " });", " if (json.results.length > 0) {", " var p = json.results[0];", " console.log('Latest:', p.predicted_class, '(' + p.confidence_percent + '%)');", " pm.test('Prediction has confidence_percent', function() {", " pm.expect(p.confidence_percent).to.be.a('number');", " });", " pm.test('Prediction has suggested_species field', function() {", " pm.expect(p).to.have.property('suggested_species');", " });", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "" ] } } }, { "name": "11. Report Wrong Prediction", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{token}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/predictions/{{predictionId}}/report_wrong/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "predictions", "{{predictionId}}", "report_wrong", "" ] } } } ] }, { "name": "Admin", "item": [ { "name": "12. Admin \u2014 Dashboard Stats", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Total users:', json.total_users);", " console.log('Total predictions:', json.total_predictions);", " console.log('Verified:', json.total_verified);", " console.log('Unknown pending:', json.unknown_predictions);", " pm.test('Stats returns all counts', function() {", " pm.expect(json.total_users).to.be.a('number');", " pm.expect(json.total_predictions).to.be.a('number');", " pm.expect(json.unknown_predictions).to.be.a('number');", " });", " pm.test('Stats includes retraining status', function() {", " pm.expect(json.retraining).to.have.property('status');", " });", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "" ] } } }, { "name": "13. Admin \u2014 List All Predictions", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Total predictions:', json.count || json.results.length);", " pm.test('Admin list returns results', function() {", " pm.expect(json.results).to.be.an('array');", " });", " if (json.results.length > 0) {", " pm.collectionVariables.set('adminPredictionId', json.results[0].id);", " console.log('First prediction ID:', json.results[0].id);", " pm.test('Prediction has suggested_species', function() {", " pm.expect(json.results[0]).to.have.property('suggested_species');", " });", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/predictions/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "predictions", "" ] } } }, { "name": "14. Admin \u2014 List Unverified Predictions", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/predictions/?is_verified=False", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "predictions", "" ], "query": [ { "key": "is_verified", "value": "False" } ] } } }, { "name": "15. Admin \u2014 Verify Prediction", "description": "Verifies a prediction. Photo moves from unverified_dataset/ \u2192 verified_dataset/. Creates Disease record if it doesn't exist. Notifies the user. Tests that verification succeeds and returns the predicted_class.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Verified:', json.predicted_class);", " pm.test('Verify returns status = verified', function() {", " pm.expect(json.status).to.equal('verified');", " });", " pm.test('Verify returns predicted_class', function() {", " pm.expect(json.predicted_class).to.not.be.empty;", " });", "} else {", " console.log('Verify failed:', pm.response.code, JSON.stringify(pm.response.json()));", " console.log('Ensure prediction ID exists and is not already verified.');", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/verify/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "predictions", "{{adminPredictionId}}", "verify", "" ] } } }, { "name": "16. Admin \u2014 Reject Prediction", "description": "Rejects a prediction. Photo moves back from verified_dataset/ \u2192 unverified_dataset/.", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/reject/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "predictions", "{{adminPredictionId}}", "reject", "" ] } } }, { "name": "17. Admin \u2014 Reclassify Unknown", "description": "Reclassifies an Unknown prediction to a new disease class.\n1. Prediction must have predicted_class = \"Unknown\"\n2. new_class must NOT already exist in the Disease table\n3. Photo moves from unverified_dataset/unknown/ \u2192 verified_dataset/{new_class}/\n4. A new Disease record is auto-created\n5. A new Treatment record can be added later via the admin portal\n\nSaves the new class name to {{newDiseaseClass}} for follow-up tests.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Reclassified to:', json.new_class);", " pm.collectionVariables.set('newDiseaseClass', json.new_class);", " pm.test('Reclassify returns new_class', function() {", " pm.expect(json.new_class).to.not.be.empty;", " });", " pm.test('Reclassify returns status', function() {", " pm.expect(json.status).to.equal('reclassified');", " });", "} else {", " console.log('Reclassify failed:', pm.response.code, JSON.stringify(pm.response.json()));", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "body": { "mode": "raw", "raw": "{\n \"new_class\": \"New_Disease_Name\",\n \"common_name\": \"New Disease Name\",\n \"plant_type\": \"Tomato\",\n \"description\": \"Optional description for this new disease.\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/reclassify/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "predictions", "{{adminPredictionId}}", "reclassify", "" ] } } }, { "name": "17b. Admin \u2014 Verify Reclassify Creates New Disease", "description": "After reclassifying an Unknown prediction, search for the new disease class to confirm it was created in the database. Tests the full reclassify flow: Unknown \u2192 new class \u2192 Disease record exists.", "event": [ { "listen": "test", "script": { "exec": [ "if (pm.response.code === 200) {", " var json = pm.response.json();", " console.log('Search results:', json.count || json.results.length);", " var found = false;", " var newClass = pm.collectionVariables.get('newDiseaseClass');", " if (newClass) {", " json.results.forEach(function(d) {", " if (d.class_name === newClass) {", " found = true;", " console.log('Found new disease:', d.common_name);", " console.log('Description:', d.description.substring(0, 80) + '...');", " pm.test('New disease has description', function() {", " pm.expect(d.description).to.not.be.empty;", " });", " }", " });", " pm.test('Reclassified disease found in database', function() {", " pm.expect(found).to.be.true;", " });", " if (!found) {", " console.log('Disease ' + newClass + ' not found in results. Available:');", " json.results.forEach(function(d) { console.log(' -', d.class_name); });", " }", " } else {", " console.log('No newDiseaseClass set. Run Reclassify (17) first.');", " }", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/diseases/?search={{newDiseaseClass}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "diseases", "" ], "query": [ { "key": "search", "value": "{{newDiseaseClass}}" } ] } } }, { "name": "18. Admin \u2014 Auto-Verify High Confidence", "description": "Auto-verifies all unverified predictions with confidence >= threshold (default 90%).\nOnly applies to predictions whose class already exists in the Disease table.\nSkips Unknown predictions.", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"auto_verify\",\n \"threshold\": 0.9\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "" ] } } }, { "name": "19. Admin \u2014 Trigger Model Retraining (Train Model Test)", "description": "Starts model retraining in the background (async). Requires at least 20 images per class in verified_dataset/ unless 'force' is set to 'true'.\n\n## Model Training Test\nThis triggers retrain_pipeline.py which:\n1. Scans verified_dataset/ folders to auto-detect classes\n2. Rebuilds the output layer with the correct number of classes\n3. Fine-tunes the model with data augmentation\n4. Saves the updated model and class names\n5. Recomputes centroids for novelty detection\n\nCheck retrain_status.json in /media/ or run Dashboard Stats (#12) to see training status.", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Authorization", "value": "Bearer {{adminToken}}" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"retrain\",\n \"force\": \"true\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "" ] } } } ] }, { "name": "API Docs", "item": [ { "name": "20. Swagger UI", "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/docs/", "host": [ "{{baseUrl}}" ], "path": [ "api", "docs", "" ] } } }, { "name": "21. OpenAPI Schema (JSON)", "request": { "method": "GET", "url": { "raw": "{{baseUrl}}/api/schema/", "host": [ "{{baseUrl}}" ], "path": [ "api", "schema", "" ] } } } ] } ] }