diff --git a/__tests__/integration/capture_denormalized.spec.js b/__tests__/integration/capture_denormalized.spec.js index e7015b9..d618006 100644 --- a/__tests__/integration/capture_denormalized.spec.js +++ b/__tests__/integration/capture_denormalized.spec.js @@ -364,6 +364,7 @@ describe('capture_denormalized', () => { 'species', 'captures', 'unverified_captures', + 'matched_captures', 'top_planters', 'trees_per_planters', 'last_updated_at', @@ -381,6 +382,7 @@ describe('capture_denormalized', () => { 'total', 'unverified_captures', ]); + expect(res.body.matched_captures).to.have.keys(['total', 'matched_captures']); expect(res.body.top_planters).to.have.keys([ 'average', 'top_planters', @@ -396,6 +398,7 @@ describe('capture_denormalized', () => { checkObjectProperties( res.body.unverified_captures.unverified_captures, ); + checkObjectProperties(res.body.matched_captures.matched_captures); checkObjectProperties(res.body.top_planters.top_planters); checkObjectProperties(res.body.trees_per_planters.trees_per_planters); checkObjectProperties(res.body.catchments.catchments); @@ -426,7 +429,7 @@ describe('capture_denormalized', () => { .end(function (err, res) { if (err) return done(err); expect(res.body.message).to.eql( - '"card_title" must be one of [planters, species, captures, unverified_captures, top_planters, trees_per_planters, catchments, gender_details, approval_rates]', + '"card_title" must be one of [planters, species, captures, unverified_captures, matched_captures, top_planters, trees_per_planters, catchments, gender_details, approval_rates]', ); return done(); }); diff --git a/database/migrations/20230724231226-add-tree-id-column.js b/database/migrations/20230724231226-add-tree-id-column.js new file mode 100644 index 0000000..f6d1310 --- /dev/null +++ b/database/migrations/20230724231226-add-tree-id-column.js @@ -0,0 +1,53 @@ +'use strict'; + +var dbm; +var type; +var seed; +var fs = require('fs'); +var path = require('path'); +var Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function(options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function(db) { + var filePath = path.join(__dirname, 'sqls', '20230724231226-add-tree-id-column-up.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports.down = function(db) { + var filePath = path.join(__dirname, 'sqls', '20230724231226-add-tree-id-column-down.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports._meta = { + "version": 1 +}; diff --git a/database/migrations/sqls/20230724231226-add-tree-id-column-down.sql b/database/migrations/sqls/20230724231226-add-tree-id-column-down.sql new file mode 100644 index 0000000..5b28223 --- /dev/null +++ b/database/migrations/sqls/20230724231226-add-tree-id-column-down.sql @@ -0,0 +1 @@ +ALTER TABLE capture_denormalized DROP COLUMN tree_id; \ No newline at end of file diff --git a/database/migrations/sqls/20230724231226-add-tree-id-column-up.sql b/database/migrations/sqls/20230724231226-add-tree-id-column-up.sql new file mode 100644 index 0000000..4cb7e57 --- /dev/null +++ b/database/migrations/sqls/20230724231226-add-tree-id-column-up.sql @@ -0,0 +1 @@ +ALTER TABLE capture_denormalized ADD COLUMN tree_id uuid; \ No newline at end of file diff --git a/server/handlers/captureHandler.js b/server/handlers/captureHandler.js index 4e28097..c6573d1 100644 --- a/server/handlers/captureHandler.js +++ b/server/handlers/captureHandler.js @@ -65,6 +65,7 @@ const captureStatisticsGetCardQuerySchema = Joi.object({ 'species', 'captures', 'unverified_captures', + 'matched_captures', 'top_planters', 'trees_per_planters', 'catchments', diff --git a/server/models/Capture.js b/server/models/Capture.js index 0cd7f42..763111a 100644 --- a/server/models/Capture.js +++ b/server/models/Capture.js @@ -94,6 +94,8 @@ class Capture { topCatchment = [], genderCount = [], approvalRates, + totalMatchedCaptures = undefined, + topMatchedCaptures = [], }) { const planters = { total: totalGrowers, @@ -170,11 +172,22 @@ class Capture { }; }); + const matched_captures = { + total: totalMatchedCaptures, + matched_captures: topMatchedCaptures.map(({ planting_organization_name, count }) => { + return { + name: planting_organization_name, + number: count, + } + }) + } + return { planters, species, captures, unverified_captures, + matched_captures, top_planters, trees_per_planters, last_updated_at, @@ -203,6 +216,8 @@ class Capture { topCatchment, genderCount, approvalRates, + totalMatchedCaptures, + topMatchedCaptures, } = await this._captureRepository.getStatistics(filter); return this.constructor.generateFormattedResponse({ @@ -223,6 +238,8 @@ class Capture { topCatchment, genderCount, approvalRates, + totalMatchedCaptures, + topMatchedCaptures, }); } diff --git a/server/repositories/CaptureRepository.js b/server/repositories/CaptureRepository.js index 28ae075..6a18dbd 100644 --- a/server/repositories/CaptureRepository.js +++ b/server/repositories/CaptureRepository.js @@ -363,6 +363,24 @@ class CaptureRepository extends BaseRepository { .limit(options.limit) .offset(options.offset); + + // total number of matched captures + const totalMatchedCapturesQuery = knex(this._tableName) + .count() + .whereNotNull('tree_id') + .where((builder) => whereBuilder({ ...filter, approved: true }, builder)); + + // top matched captures (by organization) + const topMatchedCapturesQuery = knex(this._tableName) + .select(knex.raw('planting_organization_name, count(*) as count')) + .whereNotNull('tree_id') + .where((builder) => whereBuilder({ ...filter, approved: true }, builder)) + .groupBy('planting_organization_uuid', 'planting_organization_name') + .orderBy('count', 'desc') + .limit(options.limit) + .offset(options.offset); + + if (filter?.card_title) { const { card_title } = filter; @@ -385,6 +403,7 @@ class CaptureRepository extends BaseRepository { await topUnverifiedCapturesQuery.cache(); return { topUnverifiedCaptures }; } + case 'top_planters': { const topPlanters = await topPlantersQuery.cache(); return { topPlanters }; @@ -402,6 +421,10 @@ class CaptureRepository extends BaseRepository { const approvalRates = await approvalRateQuery.cache(); return { approvalRates }; } + case 'matched_captures': { + const topMatchedCaptures = await topMatchedCapturesQuery.cache(); + return { topMatchedCaptures }; + } default: break; @@ -429,6 +452,8 @@ class CaptureRepository extends BaseRepository { const topCatchment = await topCatchmentQuery.cache(); const genderCount = await genderCountQuery.cache(); const approvalRates = await approvalRateQuery.cache(); + const totalMatchedCaptures = await totalMatchedCapturesQuery.cache(); + const topMatchedCaptures = await topMatchedCapturesQuery.cache(); return { totalGrowers: +totalGrowers[0].totalPlanters, @@ -449,6 +474,8 @@ class CaptureRepository extends BaseRepository { topCatchment, genderCount, approvalRates, + totalMatchedCaptures: +totalMatchedCaptures[0].count, + topMatchedCaptures, }; } }