AgriTech-Plant-AI / AgriTech_API.postman_collection.json
AbdullahMohzar's picture
Initial Deploy to HF
f7d0780
Raw
History Blame Contribute Delete
47.1 kB
{
"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",
""
]
}
}
}
]
}
]
}