Skip to content

Commit

Permalink
microcredit demographics route (#687)
Browse files Browse the repository at this point in the history
  • Loading branch information
Joao Pedro da Silva authored Jun 29, 2023
1 parent c043ca8 commit 5833a64
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 6 deletions.
7 changes: 7 additions & 0 deletions packages/api/src/controllers/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ class MicroCreditController {
.then(r => standardResponse(res, 200, true, r))
.catch(e => standardResponse(res, 400, false, '', { error: e }));
};

demographics = (req: RequestWithUser, res: Response) => {
this.microCreditService
.demographics()
.then(r => standardResponse(res, 200, true, r))
.catch(e => standardResponse(res, 400, false, '', { error: e }));
};
}

export { MicroCreditController };
23 changes: 23 additions & 0 deletions packages/api/src/routes/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,27 @@ export default (route: Router): void => {
cache(cacheIntervals.fiveMinutes),
controller.getRepaymentsHistory
);

/**
* @swagger
*
* /microcredit/demographics:
* get:
* tags:
* - "microcredit"
* summary: "Get Microcredit demographics"
* description: "Get Microcredit demographics"
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/demographics'
*/
route.get(
'/demographics',
cache(cacheIntervals.oneHour),
controller.demographics
)
};
221 changes: 220 additions & 1 deletion packages/core/src/services/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { JsonRpcProvider } from '@ethersproject/providers';
import { MicrocreditABI as MicroCreditABI } from '../../contracts';
import { BigNumber, Contract } from 'ethers';
import { config } from '../../..';
import { WhereOptions } from 'sequelize';
import { WhereOptions, literal, Op } from 'sequelize';
import { MicroCreditApplications } from '../../interfaces/microCredit/applications';
import { utils } from '@impactmarket/core';

Expand Down Expand Up @@ -239,4 +239,223 @@ export default class MicroCreditList {
docs: docs.map(d => d.toJSON())
};
};

/**
* @swagger
* components:
* schemas:
* ageRange:
* type: object
* properties:
* ageRange1:
* type: number
* description: age range 18 - 24
* ageRange2:
* type: number
* description: age range 25 - 34
* ageRange3:
* type: number
* description: age range 35 - 44
* ageRange4:
* type: number
* description: age range 45 - 54
* ageRange5:
* type: number
* description: age range 55 - 64
* ageRange6:
* type: number
* description: age range 65+
*/

/**
* @swagger
* components:
* schemas:
* demographics:
* type: object
* properties:
* gender:
* type: array
* items:
* type: object
* properties:
* country:
* type: string
* description: country
* example: BR
* male:
* type: number
* description: total user males
* female:
* type: number
* description: total user females
* undisclosed:
* type: number
* description: users with no information about gender
* totalGender:
* type: number
* description: total users
* ageRange:
* type: object
* properties:
* paid:
* $ref: '#/components/schemas/ageRange'
* pending:
* $ref: '#/components/schemas/ageRange'
* overdue:
* $ref: '#/components/schemas/ageRange'
*
*/
public demographics = async () => {
try {
// get all borrower addresses
const limit = 100;
const addresses: {
paid: string[],
pending: string[],
overdue: string[],
} = {
paid: [],
pending: [],
overdue: [],
};

for (let i = 0; ; i += limit) {
const rawBorrowers = await getBorrowers({ limit, offset: i, claimed: true, filter: 'all' });
if (rawBorrowers.borrowers.length === 0)
break;

rawBorrowers.borrowers.forEach((b) => {
// create payment status
if (b.lastDebt === '0') {
addresses.paid.push(getAddress(b.borrower!.id));
} else {
const limitDate = new Date();
const claimed = new Date(b.claimed*1000);
limitDate.setSeconds(claimed.getSeconds() + b.period);

if (limitDate > new Date()) {
addresses.overdue.push(getAddress(b.borrower!.id));
} else {
addresses.pending.push(getAddress(b.borrower!.id));
}
}
});
}

const year = new Date().getUTCFullYear();
const ageAttributes: any[] = [
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 18 AND 24)`
),
'ageRange1',
],
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 25 AND 34)`
),
'ageRange2',
],
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 35 AND 44)`
),
'ageRange3',
],
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 45 AND 54)`
),
'ageRange4',
],
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 55 AND 64)`
),
'ageRange5',
],
[
literal(
`count(*) FILTER (WHERE ${year}-year BETWEEN 65 AND 120)`
),
'ageRange6',
],
];
const genderAttributes: any[] = [
'country',
[
literal(
'count(*) FILTER (WHERE gender = \'m\')'
),
'male',
],
[
literal(
'count(*) FILTER (WHERE gender = \'f\')'
),
'female',
],
[
literal(
'count(*) FILTER (WHERE gender = \'u\' OR gender is null)'
),
'undisclosed',
],
[literal('count(*)'), 'totalGender'],
]

// get age range and gender by payment status
const [overdue, pending, paid, gender] = await Promise.all([
models.appUser.findAll({
attributes: ageAttributes,
where: {
address: {
[Op.in]: addresses.overdue
}
},
raw: true,
}),
models.appUser.findAll({
attributes: ageAttributes,
where: {
address: {
[Op.in]: addresses.pending
}
},
raw: true,
}),
models.appUser.findAll({
attributes: ageAttributes,
where: {
address: {
[Op.in]: addresses.paid
}
},
raw: true,
}),
models.appUser.findAll({
attributes: genderAttributes,
where: {
address: {
[Op.in]: [...addresses.paid, ...addresses.overdue, ...addresses.pending]
}
},
group: ['country'],
raw: true,
}),
]);

return {
gender,
ageRange: {
paid: paid[0],
pending: pending[0],
overdue: overdue[0],
},
};
} catch (error) {
throw new utils.BaseError('DEMOGRAPHICS_FAILED', error.message || 'failed to get microcredit demographics')
}
}
}
12 changes: 7 additions & 5 deletions packages/core/src/subgraph/queries/microcredit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ export const getMicroCreditStatsLastDays = async (
export type SubgraphGetBorrowersQuery = {
offset?: number;
limit?: number;
addedBy: string;
addedBy?: string;
claimed?: boolean;
filter?: 'repaid' | 'needHelp';
filter?: 'repaid' | 'needHelp' | 'all';
orderBy?:
| 'amount'
| 'amount:asc'
Expand All @@ -240,7 +240,7 @@ export type SubgraphGetBorrowersQuery = {
| 'lastDebt:desc';
};

const filtersToBorrowersQuery = (filter: 'repaid' | 'needHelp' | undefined): string => {
const filtersToBorrowersQuery = (filter: 'repaid' | 'needHelp' | 'all' | undefined): string => {
switch (filter) {
case 'repaid':
return 'lastDebt: 0';
Expand All @@ -249,6 +249,8 @@ const filtersToBorrowersQuery = (filter: 'repaid' | 'needHelp' | undefined): str
const date = new Date();
date.setMonth(date.getMonth() - 3);
return `lastRepayment_lte: ${Math.floor(date.getTime() / 1000)}`;
case 'all':
return '';
default:
return 'lastDebt_not: 0';
}
Expand All @@ -265,7 +267,7 @@ const countGetBorrowers = async (query: SubgraphGetBorrowersQuery): Promise<numb
first: 1000
skip: 0
where: {
addedBy: "${addedBy.toLowerCase()}"
${addedBy ? `addedBy: "${addedBy.toLowerCase()}"` : ''}
${claimed ? 'claimed_not: null' : ''}
${filtersToBorrowersQuery(filter)}
}
Expand Down Expand Up @@ -346,7 +348,7 @@ export const getBorrowers = async (
first: ${limit ? limit : 10}
skip: ${offset ? offset : 0}
where: {
addedBy: "${addedBy.toLowerCase()}"
${addedBy ? `addedBy: "${addedBy.toLowerCase()}"` : ''}
${claimed ? 'claimed_not: null' : ''}
${filtersToBorrowersQuery(filter)}
${filter === 'repaid' ? `borrower_in: ${JSON.stringify(countOrList)}` : ''}
Expand Down

0 comments on commit 5833a64

Please sign in to comment.