AbdullahMohzar/AgriTech-Plant-AI
0
1{2 "info": {3 "name": "AgriTech AI API",4 "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.",5 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"6 },7 "variable": [8 {9 "key": "baseUrl",10 "value": "http://localhost:8000"11 },12 {13 "key": "token",14 "value": ""15 },16 {17 "key": "adminToken",18 "value": ""19 },20 {21 "key": "predictionId",22 "value": ""23 },24 {25 "key": "adminPredictionId",26 "value": ""27 },28 {29 "key": "refreshToken",30 "value": ""31 },32 {33 "key": "diseaseId",34 "value": ""35 },36 {37 "key": "newDiseaseClass",38 "value": ""39 }40 ],41 "item": [42 {43 "name": "Auth",44 "item": [45 {46 "name": "1. Login \u2014 Get JWT Token",47 "event": [48 {49 "listen": "test",50 "script": {51 "exec": [52 "if (pm.response.code === 200) {",53 " var json = pm.response.json();",54 " pm.collectionVariables.set('token', json.access);",55 " pm.collectionVariables.set('refreshToken', json.refresh);",56 " console.log('Access token set:', json.access.substring(0, 20) + '...');",57 "} else {",58 " console.log('Login failed:', pm.response.code, JSON.stringify(pm.response.json()));",59 "}"60 ],61 "type": "text/javascript"62 }63 }64 ],65 "request": {66 "method": "POST",67 "header": [68 {69 "key": "Content-Type",70 "value": "application/json"71 }72 ],73 "body": {74 "mode": "raw",75 "raw": "{\n \"username\": \"testuser\",\n \"password\": \"testpass123\"\n}"76 },77 "url": {78 "raw": "{{baseUrl}}/api/v1/token/",79 "host": [80 "{{baseUrl}}"81 ],82 "path": [83 "api",84 "v1",85 "token",86 ""87 ]88 }89 }90 },91 {92 "name": "2. Login as Admin \u2014 Get JWT Token",93 "event": [94 {95 "listen": "test",96 "script": {97 "exec": [98 "if (pm.response.code === 200) {",99 " var json = pm.response.json();",100 " pm.collectionVariables.set('adminToken', json.access);",101 " pm.collectionVariables.set('refreshToken', json.refresh);",102 " console.log('Admin token set:', json.access.substring(0, 20) + '...');",103 " pm.test('Admin login successful', function() {",104 " pm.expect(json.access).to.not.be.empty;",105 " });",106 "} else {",107 " console.log('Admin login failed. Create a superuser with: python manage.py createsuperuser');",108 " console.log('Response:', pm.response.code, JSON.stringify(pm.response.json()));",109 "}"110 ],111 "type": "text/javascript"112 }113 }114 ],115 "request": {116 "method": "POST",117 "header": [118 {119 "key": "Content-Type",120 "value": "application/json"121 }122 ],123 "body": {124 "mode": "raw",125 "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}"126 },127 "url": {128 "raw": "{{baseUrl}}/api/v1/token/",129 "host": [130 "{{baseUrl}}"131 ],132 "path": [133 "api",134 "v1",135 "token",136 ""137 ]138 }139 }140 },141 {142 "name": "3. Signup \u2014 Create Account",143 "event": [144 {145 "listen": "test",146 "script": {147 "exec": [148 "if (pm.response.code === 201) {",149 " var json = pm.response.json();",150 " console.log('User created:', json.user.username);",151 " pm.test('Signup creates user with ID', function() {",152 " pm.expect(json.user.id).to.be.a('number');",153 " });",154 "} else if (pm.response.code === 400) {",155 " console.log('Signup validation error:', JSON.stringify(pm.response.json()));",156 "}"157 ],158 "type": "text/javascript"159 }160 }161 ],162 "request": {163 "method": "POST",164 "header": [165 {166 "key": "Content-Type",167 "value": "application/json"168 }169 ],170 "body": {171 "mode": "raw",172 "raw": "{\n \"username\": \"newuser\",\n \"email\": \"newuser@example.com\",\n \"password\": \"securepass123\"\n}"173 },174 "url": {175 "raw": "{{baseUrl}}/api/v1/signup/",176 "host": [177 "{{baseUrl}}"178 ],179 "path": [180 "api",181 "v1",182 "signup",183 ""184 ]185 }186 }187 },188 {189 "name": "4. Refresh Token",190 "event": [191 {192 "listen": "test",193 "script": {194 "exec": [195 "if (pm.response.code === 200) {",196 " var json = pm.response.json();",197 " pm.collectionVariables.set('token', json.access);",198 " console.log('New access token set:', json.access.substring(0, 20) + '...');",199 " pm.test('Refresh returns new access token', function() {",200 " pm.expect(json.access).to.not.be.empty;",201 " });",202 "} else {",203 " console.log('Refresh failed:', pm.response.code, JSON.stringify(pm.response.json()));",204 "}"205 ],206 "type": "text/javascript"207 }208 }209 ],210 "request": {211 "method": "POST",212 "header": [213 {214 "key": "Content-Type",215 "value": "application/json"216 }217 ],218 "body": {219 "mode": "raw",220 "raw": "{\n \"refresh\": \"{{refreshToken}}\"\n}"221 },222 "url": {223 "raw": "{{baseUrl}}/api/v1/token/refresh/",224 "host": [225 "{{baseUrl}}"226 ],227 "path": [228 "api",229 "v1",230 "token",231 "refresh",232 ""233 ]234 }235 }236 }237 ]238 },239 {240 "name": "Reference Data",241 "item": [242 {243 "name": "5. List Diseases",244 "event": [245 {246 "listen": "test",247 "script": {248 "exec": [249 "if (pm.response.code === 200) {",250 " var json = pm.response.json();",251 " console.log('Diseases found:', json.count || json.results.length);",252 " pm.test('Response has results array', function() {",253 " pm.expect(json.results).to.be.an('array');",254 " });",255 " if (json.results.length > 0) {",256 " pm.collectionVariables.set('diseaseId', json.results[0].id);",257 " console.log('First disease ID:', json.results[0].id);",258 " pm.test('Disease has description', function() {",259 " pm.expect(json.results[0].description).to.not.be.empty;",260 " });",261 " }",262 "}"263 ],264 "type": "text/javascript"265 }266 }267 ],268 "request": {269 "method": "GET",270 "url": {271 "raw": "{{baseUrl}}/api/v1/diseases/",272 "host": [273 "{{baseUrl}}"274 ],275 "path": [276 "api",277 "v1",278 "diseases",279 ""280 ]281 }282 }283 },284 {285 "name": "6. Search Diseases",286 "description": "Search diseases by name. Uses DRF SearchFilter.",287 "request": {288 "method": "GET",289 "header": [290 {291 "key": "Authorization",292 "value": "Bearer {{token}}"293 }294 ],295 "url": {296 "raw": "{{baseUrl}}/api/v1/diseases/?search=apple",297 "host": [298 "{{baseUrl}}"299 ],300 "path": [301 "api",302 "v1",303 "diseases",304 ""305 ],306 "query": [307 {308 "key": "search",309 "value": "apple"310 }311 ]312 }313 }314 },315 {316 "name": "6b. Get Disease Detail",317 "description": "Get full disease details including description, treatment (chemical + organic cure, prevention tips) and partner companies. Tests that symptoms and treatment info is available.",318 "event": [319 {320 "listen": "test",321 "script": {322 "exec": [323 "if (pm.response.code === 200) {",324 " var json = pm.response.json();",325 " console.log('Disease:', json.common_name);",326 " console.log('Description:', json.description.substring(0, 80) + '...');",327 " pm.test('Disease has description (symptoms info)', function() {",328 " pm.expect(json.description).to.not.be.empty;",329 " });",330 " if (json.treatment) {",331 " console.log('Chemical cure:', json.treatment.chemical_cure);",332 " console.log('Organic cure:', json.treatment.organic_cure);",333 " console.log('Prevention tips:', json.treatment.prevention_tips);",334 " pm.test('Treatment has chemical cure (medicines)', function() {",335 " pm.expect(json.treatment.chemical_cure).to.not.be.empty;",336 " });",337 " pm.test('Treatment has organic cure', function() {",338 " pm.expect(json.treatment.organic_cure).to.not.be.empty;",339 " });",340 " pm.test('Treatment has prevention tips', function() {",341 " pm.expect(json.treatment.prevention_tips).to.not.be.empty;",342 " });",343 " }",344 " if (json.partners && json.partners.length > 0) {",345 " console.log('Partner companies:', json.partners.length);",346 " pm.test('Partners include company names', function() {",347 " pm.expect(json.partners[0].name).to.not.be.empty;",348 " });",349 " }",350 "}"351 ],352 "type": "text/javascript"353 }354 }355 ],356 "request": {357 "method": "GET",358 "url": {359 "raw": "{{baseUrl}}/api/v1/diseases/{{diseaseId}}/",360 "host": [361 "{{baseUrl}}"362 ],363 "path": [364 "api",365 "v1",366 "diseases",367 "{{diseaseId}}",368 ""369 ]370 }371 }372 },373 {374 "name": "7. List Treatments",375 "request": {376 "method": "GET",377 "url": {378 "raw": "{{baseUrl}}/api/v1/treatments/",379 "host": [380 "{{baseUrl}}"381 ],382 "path": [383 "api",384 "v1",385 "treatments",386 ""387 ]388 }389 }390 },391 {392 "name": "8. List Companies",393 "request": {394 "method": "GET",395 "url": {396 "raw": "{{baseUrl}}/api/v1/companies/",397 "host": [398 "{{baseUrl}}"399 ],400 "path": [401 "api",402 "v1",403 "companies",404 ""405 ]406 }407 }408 },409 {410 "name": "11b. Search Disease by Species",411 "description": "Search diseases by plant species name. Tests that species-linked disease records are searchable in the API.",412 "event": [413 {414 "listen": "test",415 "script": {416 "exec": [417 "if (pm.response.code === 200) {",418 " var json = pm.response.json();",419 " console.log('Results for species search:', json.count || json.results.length);",420 " pm.test('Search returns results', function() {",421 " pm.expect(json.results).to.be.an('array');",422 " });",423 " if (json.results.length > 0) {",424 " json.results.forEach(function(d) {",425 " console.log(' -', d.class_name, '(' + d.plant_type + ')');",426 " pm.test('Disease ' + d.class_name + ' has plant_type', function() {",427 " pm.expect(d.plant_type).to.not.be.empty;",428 " });",429 " });",430 " } else {",431 " console.log('No results for this species. Try a different search term.');",432 " }",433 "}"434 ],435 "type": "text/javascript"436 }437 }438 ],439 "request": {440 "method": "GET",441 "header": [442 {443 "key": "Authorization",444 "value": "Bearer {{token}}"445 }446 ],447 "url": {448 "raw": "{{baseUrl}}/api/v1/diseases/?search=Tomato",449 "host": [450 "{{baseUrl}}"451 ],452 "path": [453 "api",454 "v1",455 "diseases",456 ""457 ],458 "query": [459 {460 "key": "search",461 "value": "Tomato"462 }463 ]464 }465 }466 }467 ]468 },469 {470 "name": "Prediction",471 "item": [472 {473 "name": "9. Predict Disease (Upload Photo)",474 "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.",475 "event": [476 {477 "listen": "test",478 "script": {479 "exec": [480 "if (pm.response.code === 201) {",481 " var json = pm.response.json();",482 " var data = json.data;",483 " pm.collectionVariables.set('predictionId', data.id);",484 " console.log('Prediction ID:', data.id);",485 " console.log('Class:', data.predicted_class);",486 " console.log('Confidence:', data.confidence);",487 " console.log('Confidence %:', data.confidence_percent + '%');",488 "",489 " // TEST: confidence_percent is always returned",490 " pm.test('confidence_percent is a number', function() {",491 " pm.expect(data.confidence_percent).to.be.a('number');",492 " });",493 " pm.test('confidence_percent = confidence * 100', function() {",494 " pm.expect(data.confidence_percent).to.equal(data.confidence * 100);",495 " });",496 "",497 " // TEST: suggested_species is present (may be null for known, string for Unknown)",498 " pm.test('suggested_species field exists', function() {",499 " pm.expect(data).to.have.property('suggested_species');",500 " });",501 " if (data.predicted_class === 'Unknown') {",502 " console.log('Suggested species:', data.suggested_species);",503 " pm.test('suggested_species is populated for Unknown', function() {",504 " pm.expect(data.suggested_species).to.not.be.null;",505 " });",506 " }",507 "",508 " // TEST: disease_info includes treatment (symptoms, medicines)",509 " if (json.disease_info) {",510 " console.log('Disease info found for:', data.predicted_class);",511 " if (json.disease_info.treatment) {",512 " console.log('Chemical cure:', json.disease_info.treatment.chemical_cure);",513 " console.log('Organic cure:', json.disease_info.treatment.organic_cure);",514 " console.log('Prevention tips:', json.disease_info.treatment.prevention_tips);",515 " pm.test('disease_info has chemical cure', function() {",516 " pm.expect(json.disease_info.treatment.chemical_cure).to.not.be.empty;",517 " });",518 " pm.test('disease_info has organic cure', function() {",519 " pm.expect(json.disease_info.treatment.organic_cure).to.not.be.empty;",520 " });",521 " pm.test('disease_info has prevention tips', function() {",522 " pm.expect(json.disease_info.treatment.prevention_tips).to.not.be.empty;",523 " });",524 " } else {",525 " console.log('No treatment record for this disease yet.');",526 " }",527 " } else {",528 " console.log('No disease_info (Unknown or no matching disease record)');",529 " }",530 "}"531 ],532 "type": "text/javascript"533 }534 }535 ],536 "request": {537 "method": "POST",538 "header": [539 {540 "key": "Authorization",541 "value": "Bearer {{token}}"542 }543 ],544 "body": {545 "mode": "formdata",546 "formdata": [547 {548 "key": "image",549 "type": "file",550 "src": ""551 }552 ]553 },554 "url": {555 "raw": "{{baseUrl}}/api/v1/predictions/",556 "host": [557 "{{baseUrl}}"558 ],559 "path": [560 "api",561 "v1",562 "predictions",563 ""564 ]565 }566 }567 },568 {569 "name": "9b. Predict Unknown & Verify Species",570 "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.",571 "event": [572 {573 "listen": "test",574 "script": {575 "exec": [576 "if (pm.response.code === 201) {",577 " var json = pm.response.json();",578 " var data = json.data;",579 " pm.collectionVariables.set('predictionId', data.id);",580 " console.log('Prediction ID:', data.id);",581 " console.log('Predicted class:', data.predicted_class);",582 " console.log('Suggested species:', data.suggested_species);",583 " console.log('Confidence:', data.confidence);",584 "",585 " if (data.predicted_class === 'Unknown') {",586 " pm.test('Unknown prediction has suggested_species', function() {",587 " pm.expect(data.suggested_species).to.not.be.null;",588 " pm.expect(data.suggested_species).to.not.be.empty;",589 " });",590 " console.log('Model 2 identified species:', data.suggested_species);",591 " pm.collectionVariables.set('newDiseaseClass', data.suggested_species + '__Healthy');",592 " console.log('Admin can accept as:', data.suggested_species + '__Healthy');",593 " } else {",594 " console.log('Prediction had a match (not Unknown). Try a different leaf photo.');",595 " }",596 "",597 " pm.test('Response includes predicted_class', function() {",598 " pm.expect(data.predicted_class).to.not.be.empty;",599 " });",600 " pm.test('Response includes confidence', function() {",601 " pm.expect(data.confidence).to.be.a('number');",602 " });",603 "}"604 ],605 "type": "text/javascript"606 }607 }608 ],609 "request": {610 "method": "POST",611 "header": [612 {613 "key": "Authorization",614 "value": "Bearer {{token}}"615 }616 ],617 "body": {618 "mode": "formdata",619 "formdata": [620 {621 "key": "image",622 "type": "file",623 "src": ""624 }625 ]626 },627 "url": {628 "raw": "{{baseUrl}}/api/v1/predictions/",629 "host": [630 "{{baseUrl}}"631 ],632 "path": [633 "api",634 "v1",635 "predictions",636 ""637 ]638 }639 }640 },641 {642 "name": "9c. Predict Non-Plant Photo (Test Model Validation)",643 "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.",644 "event": [645 {646 "listen": "test",647 "script": {648 "exec": [649 "pm.test('Response should be 400 Bad Request', function() {",650 " pm.response.to.have.status(400);",651 "});",652 "pm.test('Response should say NonPlant', function() {",653 " var json = pm.response.json();",654 " pm.expect(json.predicted_class).to.equal('NonPlant');",655 "});"656 ],657 "type": "text/javascript"658 }659 }660 ],661 "request": {662 "method": "POST",663 "header": [664 {665 "key": "Authorization",666 "value": "Bearer {{token}}"667 }668 ],669 "body": {670 "mode": "formdata",671 "formdata": [672 {673 "key": "image",674 "type": "file",675 "src": ""676 }677 ]678 },679 "url": {680 "raw": "{{baseUrl}}/api/v1/predictions/",681 "host": [682 "{{baseUrl}}"683 ],684 "path": [685 "api",686 "v1",687 "predictions",688 ""689 ]690 }691 }692 },693 {694 "name": "9d. Predict Out-of-Distribution Leaf (Strict Novelty)",695 "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'.",696 "event": [697 {698 "listen": "test",699 "script": {700 "exec": [701 "pm.test('Response should be 201 Created', function() {",702 " pm.response.to.have.status(201);",703 "});",704 "pm.test('Prediction should be Unknown', function() {",705 " var json = pm.response.json();",706 " pm.expect(json.data.predicted_class).to.equal('Unknown');",707 "});",708 "pm.test('Confidence should be logged correctly', function() {",709 " var json = pm.response.json();",710 " pm.expect(json.data.confidence_percent).to.be.a('number');",711 "});"712 ],713 "type": "text/javascript"714 }715 }716 ],717 "request": {718 "method": "POST",719 "header": [720 {721 "key": "Authorization",722 "value": "Bearer {{token}}"723 }724 ],725 "body": {726 "mode": "formdata",727 "formdata": [728 {729 "key": "image",730 "type": "file",731 "src": ""732 }733 ]734 },735 "url": {736 "raw": "{{baseUrl}}/api/v1/predictions/",737 "host": [738 "{{baseUrl}}"739 ],740 "path": [741 "api",742 "v1",743 "predictions",744 ""745 ]746 }747 }748 },749 {750 "name": "10. List My Predictions",751 "event": [752 {753 "listen": "test",754 "script": {755 "exec": [756 "if (pm.response.code === 200) {",757 " var json = pm.response.json();",758 " console.log('My predictions:', json.count || json.results.length);",759 " pm.test('My predictions returns array', function() {",760 " pm.expect(json.results).to.be.an('array');",761 " });",762 " if (json.results.length > 0) {",763 " var p = json.results[0];",764 " console.log('Latest:', p.predicted_class, '(' + p.confidence_percent + '%)');",765 " pm.test('Prediction has confidence_percent', function() {",766 " pm.expect(p.confidence_percent).to.be.a('number');",767 " });",768 " pm.test('Prediction has suggested_species field', function() {",769 " pm.expect(p).to.have.property('suggested_species');",770 " });",771 " }",772 "}"773 ],774 "type": "text/javascript"775 }776 }777 ],778 "request": {779 "method": "GET",780 "header": [781 {782 "key": "Authorization",783 "value": "Bearer {{token}}"784 }785 ],786 "url": {787 "raw": "{{baseUrl}}/api/v1/predictions/",788 "host": [789 "{{baseUrl}}"790 ],791 "path": [792 "api",793 "v1",794 "predictions",795 ""796 ]797 }798 }799 },800 {801 "name": "11. Report Wrong Prediction",802 "request": {803 "method": "POST",804 "header": [805 {806 "key": "Authorization",807 "value": "Bearer {{token}}"808 }809 ],810 "url": {811 "raw": "{{baseUrl}}/api/v1/predictions/{{predictionId}}/report_wrong/",812 "host": [813 "{{baseUrl}}"814 ],815 "path": [816 "api",817 "v1",818 "predictions",819 "{{predictionId}}",820 "report_wrong",821 ""822 ]823 }824 }825 }826 ]827 },828 {829 "name": "Admin",830 "item": [831 {832 "name": "12. Admin \u2014 Dashboard Stats",833 "event": [834 {835 "listen": "test",836 "script": {837 "exec": [838 "if (pm.response.code === 200) {",839 " var json = pm.response.json();",840 " console.log('Total users:', json.total_users);",841 " console.log('Total predictions:', json.total_predictions);",842 " console.log('Verified:', json.total_verified);",843 " console.log('Unknown pending:', json.unknown_predictions);",844 " pm.test('Stats returns all counts', function() {",845 " pm.expect(json.total_users).to.be.a('number');",846 " pm.expect(json.total_predictions).to.be.a('number');",847 " pm.expect(json.unknown_predictions).to.be.a('number');",848 " });",849 " pm.test('Stats includes retraining status', function() {",850 " pm.expect(json.retraining).to.have.property('status');",851 " });",852 "}"853 ],854 "type": "text/javascript"855 }856 }857 ],858 "request": {859 "method": "GET",860 "header": [861 {862 "key": "Authorization",863 "value": "Bearer {{adminToken}}"864 }865 ],866 "url": {867 "raw": "{{baseUrl}}/api/v1/admin/",868 "host": [869 "{{baseUrl}}"870 ],871 "path": [872 "api",873 "v1",874 "admin",875 ""876 ]877 }878 }879 },880 {881 "name": "13. Admin \u2014 List All Predictions",882 "event": [883 {884 "listen": "test",885 "script": {886 "exec": [887 "if (pm.response.code === 200) {",888 " var json = pm.response.json();",889 " console.log('Total predictions:', json.count || json.results.length);",890 " pm.test('Admin list returns results', function() {",891 " pm.expect(json.results).to.be.an('array');",892 " });",893 " if (json.results.length > 0) {",894 " pm.collectionVariables.set('adminPredictionId', json.results[0].id);",895 " console.log('First prediction ID:', json.results[0].id);",896 " pm.test('Prediction has suggested_species', function() {",897 " pm.expect(json.results[0]).to.have.property('suggested_species');",898 " });",899 " }",900 "}"901 ],902 "type": "text/javascript"903 }904 }905 ],906 "request": {907 "method": "GET",908 "header": [909 {910 "key": "Authorization",911 "value": "Bearer {{adminToken}}"912 }913 ],914 "url": {915 "raw": "{{baseUrl}}/api/v1/admin/predictions/",916 "host": [917 "{{baseUrl}}"918 ],919 "path": [920 "api",921 "v1",922 "admin",923 "predictions",924 ""925 ]926 }927 }928 },929 {930 "name": "14. Admin \u2014 List Unverified Predictions",931 "request": {932 "method": "GET",933 "header": [934 {935 "key": "Authorization",936 "value": "Bearer {{adminToken}}"937 }938 ],939 "url": {940 "raw": "{{baseUrl}}/api/v1/admin/predictions/?is_verified=False",941 "host": [942 "{{baseUrl}}"943 ],944 "path": [945 "api",946 "v1",947 "admin",948 "predictions",949 ""950 ],951 "query": [952 {953 "key": "is_verified",954 "value": "False"955 }956 ]957 }958 }959 },960 {961 "name": "15. Admin \u2014 Verify Prediction",962 "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.",963 "event": [964 {965 "listen": "test",966 "script": {967 "exec": [968 "if (pm.response.code === 200) {",969 " var json = pm.response.json();",970 " console.log('Verified:', json.predicted_class);",971 " pm.test('Verify returns status = verified', function() {",972 " pm.expect(json.status).to.equal('verified');",973 " });",974 " pm.test('Verify returns predicted_class', function() {",975 " pm.expect(json.predicted_class).to.not.be.empty;",976 " });",977 "} else {",978 " console.log('Verify failed:', pm.response.code, JSON.stringify(pm.response.json()));",979 " console.log('Ensure prediction ID exists and is not already verified.');",980 "}"981 ],982 "type": "text/javascript"983 }984 }985 ],986 "request": {987 "method": "POST",988 "header": [989 {990 "key": "Authorization",991 "value": "Bearer {{adminToken}}"992 }993 ],994 "url": {995 "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/verify/",996 "host": [997 "{{baseUrl}}"998 ],999 "path": [1000 "api",1001 "v1",1002 "admin",1003 "predictions",1004 "{{adminPredictionId}}",1005 "verify",1006 ""1007 ]1008 }1009 }1010 },1011 {1012 "name": "16. Admin \u2014 Reject Prediction",1013 "description": "Rejects a prediction. Photo moves back from verified_dataset/ \u2192 unverified_dataset/.",1014 "request": {1015 "method": "POST",1016 "header": [1017 {1018 "key": "Authorization",1019 "value": "Bearer {{adminToken}}"1020 }1021 ],1022 "url": {1023 "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/reject/",1024 "host": [1025 "{{baseUrl}}"1026 ],1027 "path": [1028 "api",1029 "v1",1030 "admin",1031 "predictions",1032 "{{adminPredictionId}}",1033 "reject",1034 ""1035 ]1036 }1037 }1038 },1039 {1040 "name": "17. Admin \u2014 Reclassify Unknown",1041 "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.",1042 "event": [1043 {1044 "listen": "test",1045 "script": {1046 "exec": [1047 "if (pm.response.code === 200) {",1048 " var json = pm.response.json();",1049 " console.log('Reclassified to:', json.new_class);",1050 " pm.collectionVariables.set('newDiseaseClass', json.new_class);",1051 " pm.test('Reclassify returns new_class', function() {",1052 " pm.expect(json.new_class).to.not.be.empty;",1053 " });",1054 " pm.test('Reclassify returns status', function() {",1055 " pm.expect(json.status).to.equal('reclassified');",1056 " });",1057 "} else {",1058 " console.log('Reclassify failed:', pm.response.code, JSON.stringify(pm.response.json()));",1059 "}"1060 ],1061 "type": "text/javascript"1062 }1063 }1064 ],1065 "request": {1066 "method": "POST",1067 "header": [1068 {1069 "key": "Content-Type",1070 "value": "application/json"1071 },1072 {1073 "key": "Authorization",1074 "value": "Bearer {{adminToken}}"1075 }1076 ],1077 "body": {1078 "mode": "raw",1079 "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}"1080 },1081 "url": {1082 "raw": "{{baseUrl}}/api/v1/admin/predictions/{{adminPredictionId}}/reclassify/",1083 "host": [1084 "{{baseUrl}}"1085 ],1086 "path": [1087 "api",1088 "v1",1089 "admin",1090 "predictions",1091 "{{adminPredictionId}}",1092 "reclassify",1093 ""1094 ]1095 }1096 }1097 },1098 {1099 "name": "17b. Admin \u2014 Verify Reclassify Creates New Disease",1100 "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.",1101 "event": [1102 {1103 "listen": "test",1104 "script": {1105 "exec": [1106 "if (pm.response.code === 200) {",1107 " var json = pm.response.json();",1108 " console.log('Search results:', json.count || json.results.length);",1109 " var found = false;",1110 " var newClass = pm.collectionVariables.get('newDiseaseClass');",1111 " if (newClass) {",1112 " json.results.forEach(function(d) {",1113 " if (d.class_name === newClass) {",1114 " found = true;",1115 " console.log('Found new disease:', d.common_name);",1116 " console.log('Description:', d.description.substring(0, 80) + '...');",1117 " pm.test('New disease has description', function() {",1118 " pm.expect(d.description).to.not.be.empty;",1119 " });",1120 " }",1121 " });",1122 " pm.test('Reclassified disease found in database', function() {",1123 " pm.expect(found).to.be.true;",1124 " });",1125 " if (!found) {",1126 " console.log('Disease ' + newClass + ' not found in results. Available:');",1127 " json.results.forEach(function(d) { console.log(' -', d.class_name); });",1128 " }",1129 " } else {",1130 " console.log('No newDiseaseClass set. Run Reclassify (17) first.');",1131 " }",1132 "}"1133 ],1134 "type": "text/javascript"1135 }1136 }1137 ],1138 "request": {1139 "method": "GET",1140 "header": [1141 {1142 "key": "Authorization",1143 "value": "Bearer {{adminToken}}"1144 }1145 ],1146 "url": {1147 "raw": "{{baseUrl}}/api/v1/diseases/?search={{newDiseaseClass}}",1148 "host": [1149 "{{baseUrl}}"1150 ],1151 "path": [1152 "api",1153 "v1",1154 "diseases",1155 ""1156 ],1157 "query": [1158 {1159 "key": "search",1160 "value": "{{newDiseaseClass}}"1161 }1162 ]1163 }1164 }1165 },1166 {1167 "name": "18. Admin \u2014 Auto-Verify High Confidence",1168 "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.",1169 "request": {1170 "method": "POST",1171 "header": [1172 {1173 "key": "Content-Type",1174 "value": "application/json"1175 },1176 {1177 "key": "Authorization",1178 "value": "Bearer {{adminToken}}"1179 }1180 ],1181 "body": {1182 "mode": "raw",1183 "raw": "{\n \"action\": \"auto_verify\",\n \"threshold\": 0.9\n}"1184 },1185 "url": {1186 "raw": "{{baseUrl}}/api/v1/admin/",1187 "host": [1188 "{{baseUrl}}"1189 ],1190 "path": [1191 "api",1192 "v1",1193 "admin",1194 ""1195 ]1196 }1197 }1198 },1199 {1200 "name": "19. Admin \u2014 Trigger Model Retraining (Train Model Test)",