├── functions
├── .eslintignore
├── src
│ ├── graphql
│ │ ├── typeDefs
│ │ │ ├── scalars
│ │ │ │ └── Date.graphql
│ │ │ ├── queries
│ │ │ │ ├── PPEQuery.graphql
│ │ │ │ ├── Metadata.graphql
│ │ │ │ ├── HealthFacilityCapacityQuery.graphql
│ │ │ │ ├── LocationQuery.graphql
│ │ │ │ ├── RatioUniqueIndQuery.graphql
│ │ │ │ ├── TestingQuery.graphql
│ │ │ │ └── CasesQuery.graphql
│ │ │ ├── TestingFacility.graphql
│ │ │ ├── DateValue.graphql
│ │ │ ├── enums
│ │ │ │ ├── RemovalType.graphql
│ │ │ │ ├── QuarantineStatus.graphql
│ │ │ │ ├── Sex.graphql
│ │ │ │ ├── PregnancyStatus.graphql
│ │ │ │ └── HealthStatus.graphql
│ │ │ ├── DateValueFloat.graphql
│ │ │ ├── HealthFacility.graphql
│ │ │ ├── PSGCRegion.graphql
│ │ │ ├── Residence.graphql
│ │ │ ├── CountPerSex.graphql
│ │ │ ├── PSGCProvince.graphql
│ │ │ ├── TestingDailyOutput.graphql
│ │ │ ├── index.ts
│ │ │ ├── DistributionAgeGroupSex.graphql
│ │ │ ├── TestingRatioUniqueInd.graphql
│ │ │ ├── Query.graphql
│ │ │ ├── TestingAggregate.graphql
│ │ │ ├── PSGCCityMunicipality.graphql
│ │ │ ├── TestingTotals.graphql
│ │ │ ├── PPEReport.graphql
│ │ │ ├── CaseInformation.graphql
│ │ │ └── HealthFacilityCapacityReport.graphql
│ │ ├── mocks
│ │ │ ├── Date.ts
│ │ │ └── index.ts
│ │ ├── resolvers
│ │ │ ├── enums
│ │ │ │ ├── RemovalType.ts
│ │ │ │ ├── Sex.ts
│ │ │ │ └── QuarantineStatus.ts
│ │ │ ├── queries
│ │ │ │ ├── MetadataQuery.ts
│ │ │ │ ├── PPEQuery.ts
│ │ │ │ ├── HealthFacilityCapacityQuery.ts
│ │ │ │ ├── LocationQuery.ts
│ │ │ │ ├── RatioUniqueIndQuery.ts
│ │ │ │ ├── TestingQuery.ts
│ │ │ │ └── CasesQuery.ts
│ │ │ ├── index.ts
│ │ │ ├── Query.ts
│ │ │ └── scalars
│ │ │ │ └── Date.ts
│ │ └── dataSources
│ │ │ ├── PHLocations
│ │ │ ├── index.ts
│ │ │ └── PHLocations.ts
│ │ │ ├── DataDropDailyReport
│ │ │ ├── index.ts
│ │ │ ├── DataDropDailyReport.ts
│ │ │ ├── HealthFacilityCapacityReports.ts
│ │ │ └── toHealthFacilityCapacityReport.ts
│ │ │ ├── DataDropWeeklyReport
│ │ │ ├── index.ts
│ │ │ ├── DataDropWeeklyReport.ts
│ │ │ ├── PPEReports.ts
│ │ │ └── toPPEReport.ts
│ │ │ ├── DataDropCaseInformation
│ │ │ ├── index.ts
│ │ │ ├── Cases.ts
│ │ │ ├── toCaseInformation.ts
│ │ │ └── DataDropCaseInformation.ts
│ │ │ ├── DataDropTestingAggregates
│ │ │ ├── index.ts
│ │ │ ├── TestingAggregates.ts
│ │ │ ├── toTestingAggregates.ts
│ │ │ └── DataDropTestingAggregates.ts
│ │ │ └── index.ts
│ ├── consts
│ │ ├── dateFirstCase.ts
│ │ └── dateFirstTestingRecorded.ts
│ ├── utils
│ │ ├── between.ts
│ │ ├── stripAltName.ts
│ │ ├── stripFacilityName.ts
│ │ ├── toAge.ts
│ │ ├── toBoolean.ts
│ │ ├── toNullableString.ts
│ │ ├── toCity.ts
│ │ ├── toFacilityName.ts
│ │ ├── toSex.ts
│ │ ├── toNullableInt.ts
│ │ ├── toRemovalType.ts
│ │ ├── dateRangeArray.ts
│ │ ├── toPregnancyStatus.ts
│ │ ├── toQuarantined.ts
│ │ ├── toRegion.ts
│ │ ├── toDate.ts
│ │ ├── toProvince.ts
│ │ └── toHealthStatus.ts
│ ├── services
│ │ ├── index.ts
│ │ └── apollo.ts
│ ├── types
│ │ ├── DateValue.ts
│ │ ├── RemovalType.ts
│ │ ├── Sex.ts
│ │ ├── HealthFacility.ts
│ │ ├── TestingFacility.ts
│ │ ├── CountPerSex.ts
│ │ ├── PregnancyStatus.ts
│ │ ├── QuarantineStatus.ts
│ │ ├── DistributionAgeGroupSex.ts
│ │ ├── HealthStatus.ts
│ │ ├── PPEReport.ts
│ │ ├── HealthFacilityCapacityReport.ts
│ │ ├── AgeGroup.ts
│ │ ├── TestingAggregate.ts
│ │ └── CaseInformation.ts
│ ├── middlewares
│ │ ├── index.ts
│ │ ├── cors.ts
│ │ └── rateLimit.ts
│ ├── index.ts
│ ├── bin
│ │ └── serve.ts
│ ├── firebase.ts
│ ├── apollo.ts
│ ├── express.ts
│ └── environment.ts
├── ANNOTATIONS
├── tsconfig.json
├── .eslintrc.json
└── package.json
├── hosting
├── src
│ ├── react-app-env.d.ts
│ ├── main.css
│ ├── setupTests.ts
│ ├── components
│ │ ├── ExternalLink.tsx
│ │ ├── SectionTitle.tsx
│ │ ├── CodeBlockJson.tsx
│ │ ├── StarButton.tsx
│ │ ├── IssueButton.tsx
│ │ └── TryButton.tsx
│ ├── sections
│ │ ├── RateLimit.tsx
│ │ ├── DataSource.tsx
│ │ ├── License.tsx
│ │ ├── Contributing.tsx
│ │ ├── GraphQL.tsx
│ │ └── Updates.tsx
│ ├── App.tsx
│ ├── index.tsx
│ ├── Main.tsx
│ └── serviceWorker.ts
├── public
│ ├── favicon.ico
│ ├── robots.txt
│ ├── apple-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── logo1200x630.png
│ ├── ms-icon-70x70.png
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── apple-icon-precomposed.png
│ ├── browserconfig.xml
│ ├── manifest.json
│ └── index.html
├── tsconfig.json
└── package.json
├── .firebaserc
├── .gitignore
├── LICENSE
├── firebase.json
├── .github
└── stale.yml
└── README.md
/functions/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/
3 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/scalars/Date.graphql:
--------------------------------------------------------------------------------
1 | scalar Date
--------------------------------------------------------------------------------
/functions/src/consts/dateFirstCase.ts:
--------------------------------------------------------------------------------
1 | export default '2020-01-30';
2 |
--------------------------------------------------------------------------------
/hosting/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/functions/src/consts/dateFirstTestingRecorded.ts:
--------------------------------------------------------------------------------
1 | export default '2020-04-02';
2 |
--------------------------------------------------------------------------------
/functions/src/graphql/mocks/Date.ts:
--------------------------------------------------------------------------------
1 | export default (): string => '2020-03-24';
2 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "ncovidtracker-api"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/hosting/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/favicon.ico
--------------------------------------------------------------------------------
/hosting/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/hosting/public/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon.png
--------------------------------------------------------------------------------
/hosting/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/favicon-16x16.png
--------------------------------------------------------------------------------
/hosting/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/favicon-32x32.png
--------------------------------------------------------------------------------
/hosting/public/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/favicon-96x96.png
--------------------------------------------------------------------------------
/hosting/public/logo1200x630.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/logo1200x630.png
--------------------------------------------------------------------------------
/hosting/public/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/ms-icon-70x70.png
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/queries/PPEQuery.graphql:
--------------------------------------------------------------------------------
1 | # PPE query
2 | type PPEQuery {
3 | latest: [PPEReport]
4 | }
5 |
--------------------------------------------------------------------------------
/hosting/public/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/ms-icon-144x144.png
--------------------------------------------------------------------------------
/hosting/public/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/ms-icon-150x150.png
--------------------------------------------------------------------------------
/hosting/public/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/ms-icon-310x310.png
--------------------------------------------------------------------------------
/hosting/public/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-36x36.png
--------------------------------------------------------------------------------
/hosting/public/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-48x48.png
--------------------------------------------------------------------------------
/hosting/public/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-72x72.png
--------------------------------------------------------------------------------
/hosting/public/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-96x96.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-114x114.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-120x120.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-144x144.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-152x152.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-180x180.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-57x57.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-60x60.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-72x72.png
--------------------------------------------------------------------------------
/hosting/public/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-76x76.png
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/enums/RemovalType.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | RECOVERED: 'RECOVERED',
3 | DIED: 'DIED',
4 | };
5 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/TestingFacility.graphql:
--------------------------------------------------------------------------------
1 | type TestingFacility {
2 | name: String!
3 | abbreviation: String
4 | }
--------------------------------------------------------------------------------
/functions/src/utils/between.ts:
--------------------------------------------------------------------------------
1 | export default (val: number, start: number, end: number): boolean => (start <= val && val <= end);
2 |
--------------------------------------------------------------------------------
/functions/src/utils/stripAltName.ts:
--------------------------------------------------------------------------------
1 | export default (str: string): string => str.replace(/\s\([A-Za-z0-9\-\s]+\)$/, '').trim();
2 |
--------------------------------------------------------------------------------
/functions/src/utils/stripFacilityName.ts:
--------------------------------------------------------------------------------
1 | export default (name: string): string => name.replace(/[^a-zA-Z\d]/g, '').toUpperCase();
2 |
--------------------------------------------------------------------------------
/hosting/public/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-144x144.png
--------------------------------------------------------------------------------
/hosting/public/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/android-icon-192x192.png
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/PHLocations/index.ts:
--------------------------------------------------------------------------------
1 | import PHLocations from './PHLocations';
2 |
3 | export default PHLocations;
4 |
--------------------------------------------------------------------------------
/hosting/public/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hubertursua/ncovph/HEAD/hosting/public/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/functions/src/graphql/mocks/index.ts:
--------------------------------------------------------------------------------
1 | import Date from './Date';
2 |
3 | const mocks = {
4 | Date,
5 | };
6 |
7 | export default mocks;
8 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/enums/Sex.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | FEMALE: 'FEMALE',
3 | MALE: 'MALE',
4 | UNKNOWN: 'UNKNOWN',
5 | };
6 |
--------------------------------------------------------------------------------
/functions/src/services/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | export { default as apollo } from './apollo';
4 |
--------------------------------------------------------------------------------
/functions/src/types/DateValue.ts:
--------------------------------------------------------------------------------
1 | interface DateValue {
2 | date: string;
3 | value: number;
4 | }
5 |
6 | export default DateValue;
7 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/enums/QuarantineStatus.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | UNKNOWN: 'UNKNOWN',
3 | YES: 'YES',
4 | NO: 'NO',
5 | };
6 |
--------------------------------------------------------------------------------
/functions/src/types/RemovalType.ts:
--------------------------------------------------------------------------------
1 | enum RemovalType {
2 | RECOVERED = 'RECOVERED',
3 | DIED = 'DIED',
4 | }
5 |
6 | export default RemovalType;
7 |
--------------------------------------------------------------------------------
/functions/src/types/Sex.ts:
--------------------------------------------------------------------------------
1 | enum Sex {
2 | FEMALE = 'FEMALE',
3 | MALE = 'MALE',
4 | UNKNOWN = 'UNKNOWN',
5 | }
6 |
7 | export default Sex;
8 |
--------------------------------------------------------------------------------
/hosting/src/main.css:
--------------------------------------------------------------------------------
1 | .inline-code {
2 | background: rgba(0,0,0, 0.08);
3 | border-radius: 4px;
4 | padding: 0 6px;
5 | font-size: 0.87em;
6 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/queries/Metadata.graphql:
--------------------------------------------------------------------------------
1 | # Metadata Query
2 | type MetadataQuery {
3 | # Location
4 | location: LocationQuery
5 | }
6 |
--------------------------------------------------------------------------------
/functions/src/types/HealthFacility.ts:
--------------------------------------------------------------------------------
1 | interface HealthFacility {
2 | code: string;
3 |
4 | name: string;
5 | }
6 |
7 | export default HealthFacility;
8 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropDailyReport/index.ts:
--------------------------------------------------------------------------------
1 | import DataDropDailyReport from './DataDropDailyReport';
2 |
3 | export default DataDropDailyReport;
4 |
--------------------------------------------------------------------------------
/functions/src/types/TestingFacility.ts:
--------------------------------------------------------------------------------
1 | interface TestingFacility {
2 | name: string;
3 | abbreviation: string;
4 | }
5 |
6 | export default TestingFacility;
7 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropWeeklyReport/index.ts:
--------------------------------------------------------------------------------
1 | import DataDropWeeklyReport from './DataDropWeeklyReport';
2 |
3 | export default DataDropWeeklyReport;
4 |
--------------------------------------------------------------------------------
/functions/src/types/CountPerSex.ts:
--------------------------------------------------------------------------------
1 | interface CountPerSex {
2 | female: number;
3 | male: number;
4 | unknown: number;
5 | }
6 |
7 | export default CountPerSex;
8 |
--------------------------------------------------------------------------------
/functions/src/middlewares/index.ts:
--------------------------------------------------------------------------------
1 | import * as cors from './cors';
2 | import * as rateLimit from './rateLimit';
3 |
4 | export default {
5 | cors,
6 | rateLimit,
7 | };
8 |
--------------------------------------------------------------------------------
/functions/src/types/PregnancyStatus.ts:
--------------------------------------------------------------------------------
1 | enum PregnancyStatus {
2 | UNKNOWN = 'UNKNOWN',
3 | NO = 'NO',
4 | YES = 'YES',
5 | }
6 |
7 | export default PregnancyStatus;
8 |
--------------------------------------------------------------------------------
/functions/src/types/QuarantineStatus.ts:
--------------------------------------------------------------------------------
1 | enum QuarantineStatus {
2 | UNKNOWN = 'UNKNOWN',
3 | YES = 'YES',
4 | NO = 'NO',
5 | }
6 |
7 | export default QuarantineStatus;
8 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/queries/MetadataQuery.ts:
--------------------------------------------------------------------------------
1 | import LocationQuery from './LocationQuery';
2 |
3 | export default {
4 | location: (): object => LocationQuery,
5 | };
6 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/DateValue.graphql:
--------------------------------------------------------------------------------
1 | # Value per date
2 | type DateValue {
3 | # Date
4 | date: Date
5 |
6 | # Value for a particular date
7 | value: Int
8 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/enums/RemovalType.graphql:
--------------------------------------------------------------------------------
1 | # Removal type (RECOVERED, DIED)
2 | enum RemovalType {
3 | # Recovered
4 | RECOVERED
5 |
6 | # Died
7 | DIED
8 | }
--------------------------------------------------------------------------------
/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import './firebase'; // Pre-initialize firebase-admin
2 | import {
3 | apollo,
4 | } from './services';
5 |
6 | module.exports = {
7 | apollo,
8 | };
9 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropCaseInformation/index.ts:
--------------------------------------------------------------------------------
1 | import DataDropCaseInformation from './DataDropCaseInformation';
2 |
3 | export default DataDropCaseInformation;
4 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropTestingAggregates/index.ts:
--------------------------------------------------------------------------------
1 | import DataDropTestingAggregates from './DataDropTestingAggregates';
2 |
3 | export default DataDropTestingAggregates;
4 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/DateValueFloat.graphql:
--------------------------------------------------------------------------------
1 | # Value per date
2 | type DateValueFloat {
3 | # Date
4 | date: Date
5 |
6 | # Value for a particular date
7 | value: Float
8 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/queries/HealthFacilityCapacityQuery.graphql:
--------------------------------------------------------------------------------
1 | # Health Facility Capacity Query
2 | type HealthFacilityCapacityQuery {
3 | latest: [HealthFacilityCapacityReport]
4 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/enums/QuarantineStatus.graphql:
--------------------------------------------------------------------------------
1 | # Quarantine status
2 | enum QuarantineStatus {
3 | # Unknown
4 | UNKNOWN
5 |
6 | # Yes
7 | YES
8 |
9 | # No
10 | NO
11 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/enums/Sex.graphql:
--------------------------------------------------------------------------------
1 | # Sex (FEMALE, MALE, UNKNOWN)
2 | enum Sex {
3 | # Female
4 | FEMALE
5 |
6 | # Male
7 | MALE
8 |
9 | # Unknown
10 | UNKNOWN
11 | }
12 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/enums/PregnancyStatus.graphql:
--------------------------------------------------------------------------------
1 | # Pregnancy Status
2 | enum PregnancyStatus {
3 | # Unknown
4 | UNKNOWN
5 |
6 | # No
7 | NO
8 |
9 | # Yes
10 | YES
11 | }
12 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/HealthFacility.graphql:
--------------------------------------------------------------------------------
1 | # Health Facility
2 | type HealthFacility {
3 | # Health facility's identification code (hfhudcode)
4 | code: String
5 |
6 | # Name
7 | name: String
8 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/PSGCRegion.graphql:
--------------------------------------------------------------------------------
1 | # Region
2 | type PSGCRegion {
3 | # Code
4 | code: String!
5 |
6 | # Name
7 | name: String!
8 |
9 | # Alternate name
10 | altName: String
11 | }
12 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/Residence.graphql:
--------------------------------------------------------------------------------
1 | # Residence
2 | type Residence {
3 | # Region
4 | region: String
5 |
6 | # Province
7 | province: String
8 |
9 | # City or municipality
10 | city: String
11 | }
--------------------------------------------------------------------------------
/functions/src/types/DistributionAgeGroupSex.ts:
--------------------------------------------------------------------------------
1 | interface DistributionAgeGroupSex {
2 | ageGroup: string;
3 | female: number;
4 | male: number;
5 | unknown: number;
6 | }
7 |
8 | export default DistributionAgeGroupSex;
9 |
--------------------------------------------------------------------------------
/functions/src/utils/toAge.ts:
--------------------------------------------------------------------------------
1 | export default (str: string): number | null => {
2 | const val = Number.parseInt(str, 10);
3 |
4 | if (Number.isNaN(val)) {
5 | return null;
6 | }
7 |
8 | return val;
9 | };
10 |
--------------------------------------------------------------------------------
/functions/src/utils/toBoolean.ts:
--------------------------------------------------------------------------------
1 | export default (str: string): boolean => {
2 | const val = str.toUpperCase().trim();
3 |
4 | if (['TRUE', 'YES', 'T', 'Y'].includes(val)) {
5 | return true;
6 | }
7 |
8 | return false;
9 | };
10 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/CountPerSex.graphql:
--------------------------------------------------------------------------------
1 | # Total count per sex
2 | type CountPerSex {
3 | # Total for females
4 | female: Int
5 |
6 | # Total for males
7 | male: Int
8 |
9 | # Total for unknowns
10 | unknown: Int
11 | }
12 |
--------------------------------------------------------------------------------
/functions/ANNOTATIONS:
--------------------------------------------------------------------------------
1 | [April 22, 2020]
2 |
3 | Testing Aggregates
4 |
5 | 1. April 21, 2020 Philippine Red Cross (PRC) Remaining Available Tests was
6 | changed from "≈70000" to "70000". The approximation was removed and pegged at
7 | the lowest number.
--------------------------------------------------------------------------------
/functions/src/middlewares/cors.ts:
--------------------------------------------------------------------------------
1 | import cors from 'cors';
2 |
3 | const options = {
4 | origin: '*',
5 | optionsSuccessStatus: 200,
6 | };
7 |
8 | // eslint-disable-next-line import/prefer-default-export
9 | export const handler = cors(options);
10 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/PSGCProvince.graphql:
--------------------------------------------------------------------------------
1 | # Province
2 | type PSGCProvince {
3 | # Code
4 | code: String!
5 |
6 | # Name
7 | name: String!
8 |
9 | # Alternate name
10 | altName: String
11 |
12 | # Region code
13 | region: String!
14 | }
15 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/queries/LocationQuery.graphql:
--------------------------------------------------------------------------------
1 | # Location Query
2 | type LocationQuery {
3 | # Regions
4 | regions: [PSGCRegion]
5 |
6 | # Provinces
7 | provinces: [PSGCProvince]
8 |
9 | # Cities
10 | cities: [PSGCCityMunicipality]
11 | }
12 |
--------------------------------------------------------------------------------
/functions/src/utils/toNullableString.ts:
--------------------------------------------------------------------------------
1 | export default (str: string): string | null => {
2 | if (!str) {
3 | return null;
4 | }
5 |
6 | const val = str.trim();
7 |
8 | if (val === '') {
9 | return null;
10 | }
11 |
12 |
13 | return val;
14 | };
15 |
--------------------------------------------------------------------------------
/functions/src/bin/serve.ts:
--------------------------------------------------------------------------------
1 | import environment from '../environment';
2 | import express from '../express';
3 |
4 | express.listen({ port: environment.port }, () => {
5 | // eslint-disable-next-line no-console
6 | console.log(`Server ready at http://localhost:${environment.port}`);
7 | });
8 |
--------------------------------------------------------------------------------
/hosting/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/functions/src/types/HealthStatus.ts:
--------------------------------------------------------------------------------
1 | enum HealthStatus {
2 | UNKNOWN = 'UNKNOWN',
3 | ASYMPTOMATIC = 'ASYMPTOMATIC',
4 | MILD = 'MILD',
5 | SEVERE = 'SEVERE',
6 | CRITICAL = 'CRITICAL',
7 | RECOVERED = 'RECOVERED',
8 | DIED = 'DIED',
9 | }
10 |
11 | export default HealthStatus;
12 |
--------------------------------------------------------------------------------
/hosting/src/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function ExternalLink({ href, label }: { href: string, label: string }) {
4 | return (
5 |
6 | {label}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/hosting/src/components/SectionTitle.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Heading } from "grommet";
3 |
4 | export default function SectionTitle({ title }: { title: string; }) {
5 | return (
6 |
7 | {title}
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/functions/src/services/apollo.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as functions from 'firebase-functions';
3 | import express from '../express';
4 |
5 | export default functions
6 | .region('us-central1')
7 | .runWith({
8 | memory: '256MB',
9 | timeoutSeconds: 30,
10 | })
11 | .https.onRequest(express);
12 |
--------------------------------------------------------------------------------
/hosting/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/TestingDailyOutput.graphql:
--------------------------------------------------------------------------------
1 | type TestingDailyOutput {
2 | # Daily Output - Positive Individuals
3 | positiveInd: Int
4 |
5 | # Daily Output - Unique Individuals Tested
6 | uniqueIndTested: Int
7 |
8 | # Daily Output - Tests Conducted
9 | testsConducted: Int
10 | }
11 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/index.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import glob from 'glob';
3 |
4 | const files = glob.sync(`${__dirname}/**/*.graphql`);
5 |
6 | const typeDefs = files
7 | .map((file) => fs.readFileSync(file, { encoding: 'utf8' }).trim())
8 | .join('\n\n');
9 |
10 | export default typeDefs;
11 |
--------------------------------------------------------------------------------
/functions/src/utils/toCity.ts:
--------------------------------------------------------------------------------
1 | import stripAltName from './stripAltName';
2 |
3 | export default (city: string | null): string | null => {
4 | if (!city) {
5 | return null;
6 | }
7 |
8 | const citySanitized = stripAltName(city).replace('For Validation', '') || null;
9 |
10 | return citySanitized;
11 | };
12 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/DistributionAgeGroupSex.graphql:
--------------------------------------------------------------------------------
1 | # Distribution of sex per age group
2 | type DistributionAgeGroupSex {
3 | # Age group
4 | ageGroup: String
5 |
6 | # Total for females
7 | female: Int
8 |
9 | # Total for males
10 | male: Int
11 |
12 | # Total for unknowns
13 | unknown: Int
14 | }
15 |
--------------------------------------------------------------------------------
/functions/src/utils/toFacilityName.ts:
--------------------------------------------------------------------------------
1 | import stripAltName from './stripAltName';
2 | import toNullableString from './toNullableString';
3 |
4 | export default (str: string): string | null => {
5 | const val = toNullableString(str);
6 |
7 | if (val === null) {
8 | return null;
9 | }
10 |
11 | return stripAltName(val);
12 | };
13 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/enums/HealthStatus.graphql:
--------------------------------------------------------------------------------
1 | # Health status
2 | enum HealthStatus {
3 | # Unknown
4 | UNKNOWN
5 |
6 | # Asymptomatic
7 | ASYMPTOMATIC
8 |
9 | # Mild
10 | MILD
11 |
12 | # Severe
13 | SEVERE
14 |
15 | # Critical
16 | CRITICAL
17 |
18 | # Recovered
19 | RECOVERED
20 |
21 | # Died
22 | DIED
23 | }
24 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/TestingRatioUniqueInd.graphql:
--------------------------------------------------------------------------------
1 | type TestingRatioUniqueInd {
2 | # % positive/ unique individuals
3 | positiveInd: Float
4 |
5 | # % negative/unique individuals
6 | negativeInd: Float
7 |
8 | # % equivocal /unique individuals
9 | equivocalInd: Float
10 |
11 | # % invalid /unique individuals
12 | invalidInd: Float
13 | }
--------------------------------------------------------------------------------
/functions/src/utils/toSex.ts:
--------------------------------------------------------------------------------
1 | import Sex from '../types/Sex';
2 |
3 | export default (str: string): Sex | null => {
4 | const val = str.toUpperCase().trim();
5 |
6 | if (['F', 'FEMALE'].includes(val)) {
7 | return Sex.FEMALE;
8 | }
9 |
10 | if (['M', 'MALE'].includes(val)) {
11 | return Sex.MALE;
12 | }
13 |
14 | return Sex.UNKNOWN;
15 | };
16 |
--------------------------------------------------------------------------------
/functions/src/utils/toNullableInt.ts:
--------------------------------------------------------------------------------
1 | export default (str: string | number): number | null => {
2 | if (str === null) {
3 | return null;
4 | }
5 |
6 | const val = (typeof str === 'number')
7 | ? str.toString()
8 | : str.replace(',', '').trim();
9 |
10 | if (val === '') {
11 | return null;
12 | }
13 |
14 | return parseInt(val, 10);
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env*
2 | dist
3 | build
4 | node_modules
5 | .idea
6 | .vscode
7 | *.log
8 |
9 | /package.json
10 | /package-lock.json
11 |
12 | ## Compiled JavaScript files
13 | **/*.js
14 | **/*.js.map
15 |
16 | # Typescript v1 declaration files
17 | typings/
18 |
19 | # Firebase
20 | firebase-debug.log
21 | .firebase
22 | serviceAccountKey.json
23 |
24 | # App-specific
25 | functions/tmp
--------------------------------------------------------------------------------
/functions/src/utils/toRemovalType.ts:
--------------------------------------------------------------------------------
1 | import RemovalType from '../types/RemovalType';
2 |
3 | export default (str: string): RemovalType | null => {
4 | const val = str.toUpperCase().trim();
5 |
6 | if (val === 'RECOVERED') {
7 | return RemovalType.RECOVERED;
8 | }
9 |
10 | if (val === 'DIED') {
11 | return RemovalType.DIED;
12 | }
13 |
14 | return null;
15 | };
16 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/Query.graphql:
--------------------------------------------------------------------------------
1 | # Query
2 | type Query {
3 | # Hello
4 | hello: String
5 |
6 | # Cases
7 | cases: CasesQuery
8 |
9 | # Testing
10 | testing: TestingQuery
11 |
12 | # PPE
13 | ppe: PPEQuery
14 |
15 | # Health Facility Capacity
16 | healthFacilityCapacity: HealthFacilityCapacityQuery
17 |
18 | # Metadata
19 | metadata: MetadataQuery
20 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/TestingAggregate.graphql:
--------------------------------------------------------------------------------
1 | type TestingAggregate {
2 | # Date
3 | date: Date!
4 |
5 | # Facility
6 | facility: TestingFacility!
7 |
8 | # Daily output
9 | dailyOutput: TestingDailyOutput!
10 |
11 | # Testing totals
12 | totals: TestingTotals!
13 |
14 | # Ratio over unique indiduals tested
15 | ratioUniqueInd: TestingRatioUniqueInd!
16 | }
17 |
--------------------------------------------------------------------------------
/hosting/src/sections/RateLimit.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, Paragraph } from "grommet";
3 | import SectionTitle from "../components/SectionTitle";
4 |
5 | export default function RateLimit(): JSX.Element {
6 | return (
7 |
8 |
9 | 45 requests per minute
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/functions/src/middlewares/rateLimit.ts:
--------------------------------------------------------------------------------
1 | import expressRateLimit from 'express-rate-limit';
2 | import environment from '../environment';
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export const handler = expressRateLimit({
6 | windowMs: environment.rateLimit.windowMs,
7 | max: environment.rateLimit.max,
8 | message: { error: 'Too many requests. Slow down.' },
9 | });
10 |
--------------------------------------------------------------------------------
/hosting/src/components/CodeBlockJson.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SyntaxHighlighter from "react-syntax-highlighter";
3 | import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
4 |
5 | export default function CodeBlockJson({ code }: { code: string }) {
6 | return (
7 |
8 | {code}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/hosting/src/components/StarButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import GitHubButton from "react-github-btn";
3 |
4 | export default function StarButton() {
5 | return (
6 |
12 | Star
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/functions/src/utils/dateRangeArray.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export default (start: string, end: string = null): string[] => {
4 | const arr = [];
5 | const pointer = moment(start);
6 | const endPointer = moment(end || new Date());
7 |
8 | while (pointer.valueOf() <= endPointer.valueOf()) {
9 | arr.push(pointer.format('YYYY-MM-DD'));
10 | pointer.add(1, 'day');
11 | }
12 |
13 | return arr;
14 | };
15 |
--------------------------------------------------------------------------------
/functions/src/utils/toPregnancyStatus.ts:
--------------------------------------------------------------------------------
1 | import PregnancyStatus from '../types/PregnancyStatus';
2 | import toNullableString from './toNullableString';
3 |
4 | export default (str: string): PregnancyStatus => {
5 | const val = toNullableString(str);
6 |
7 | if (!val) {
8 | return PregnancyStatus.UNKNOWN;
9 | }
10 |
11 | return (val.toUpperCase() === 'YES')
12 | ? PregnancyStatus.YES
13 | : PregnancyStatus.NO;
14 | };
15 |
--------------------------------------------------------------------------------
/hosting/src/components/IssueButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import GitHubButton from "react-github-btn";
3 |
4 | export default function IssueButton() {
5 | return (
6 |
12 | Issue
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/functions/src/utils/toQuarantined.ts:
--------------------------------------------------------------------------------
1 | import QuarantineStatus from '../types/QuarantineStatus';
2 | import toNullableString from './toNullableString';
3 |
4 | export default (str: string): QuarantineStatus => {
5 | const val = toNullableString(str);
6 |
7 | if (!val) {
8 | return QuarantineStatus.UNKNOWN;
9 | }
10 |
11 | return (val.toUpperCase() === 'YES')
12 | ? QuarantineStatus.YES
13 | : QuarantineStatus.NO;
14 | };
15 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/queries/PPEQuery.ts:
--------------------------------------------------------------------------------
1 | import PPEReport from '../../../types/PPEReport';
2 | import { DataSources } from '../../dataSources';
3 |
4 | export interface Context {
5 | dataSources: DataSources;
6 | }
7 |
8 | export default {
9 | latest: (
10 | _obj: unknown,
11 | _args: unknown,
12 | { dataSources }: Context,
13 | ): PPEReport[] => dataSources
14 | .DataDropWeeklyReport
15 | .getLatest(),
16 | };
17 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/PSGCCityMunicipality.graphql:
--------------------------------------------------------------------------------
1 | # City and Municipality
2 | type PSGCCityMunicipality {
3 | # Code
4 | code: String!
5 |
6 | # Name
7 | name: String!
8 |
9 | # Full name
10 | fullName: String!
11 |
12 | # Alternate name
13 | altName: String
14 |
15 | # Province code
16 | province: String!
17 |
18 | # Classification (MUNICIPALITY or CITY)
19 | classification: String!
20 |
21 | # Is capital
22 | isCapital: Boolean!
23 | }
24 |
--------------------------------------------------------------------------------
/hosting/src/components/TryButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "grommet";
3 |
4 | export default function TryButton({ query }: {
5 | query: string;
6 | }): JSX.Element {
7 | return (
8 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/queries/RatioUniqueIndQuery.graphql:
--------------------------------------------------------------------------------
1 | # Ratio with Unique Individuals Query
2 | type RatioUniqueIndQuery {
3 | # Ratio of positive individuals to unique individuals
4 | positiveInd: Float
5 |
6 | # Ratio of negative individuals to unique individuals
7 | negativeInd: Float
8 |
9 | # Ratio of invalid individuals to unique individuals
10 | invalidInd: Float
11 |
12 | # Ratio of equivocal individuals to unique individuals
13 | equivocalInd: Float
14 | }
--------------------------------------------------------------------------------
/functions/src/types/PPEReport.ts:
--------------------------------------------------------------------------------
1 | import HealthFacility from './HealthFacility';
2 |
3 | interface PPEReport {
4 | healthFacility: HealthFacility;
5 |
6 | dateReported: string;
7 |
8 | gowns: number;
9 |
10 | gloves: number;
11 |
12 | headCovers: number;
13 |
14 | goggles: number;
15 |
16 | coveralls: number;
17 |
18 | shoeCovers: number;
19 |
20 | faceShields: number;
21 |
22 | surgicalMasks: number;
23 |
24 | n95Masks: number;
25 | }
26 |
27 | export default PPEReport;
28 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/queries/HealthFacilityCapacityQuery.ts:
--------------------------------------------------------------------------------
1 | import HealthFacilityCapacityReport from '../../../types/HealthFacilityCapacityReport';
2 | import { DataSources } from '../../dataSources';
3 |
4 | export interface Context {
5 | dataSources: DataSources;
6 | }
7 |
8 | export default {
9 | latest: (
10 | _obj: unknown,
11 | _args: unknown,
12 | { dataSources }: Context,
13 | ): HealthFacilityCapacityReport[] => dataSources
14 | .DataDropDailyReport
15 | .getLatest(),
16 | };
17 |
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/TestingTotals.graphql:
--------------------------------------------------------------------------------
1 | type TestingTotals {
2 | # Total Unique Individuals Tested
3 | uniqueIndTested: Int
4 |
5 | # Total Positive Individuals
6 | positiveInd: Int
7 |
8 | # Total Negative Individuals
9 | negativeInd: Int
10 |
11 | # Total Equivocal Individuals
12 | equivocalInd: Int
13 |
14 | # Total Invalid Individuals
15 | invalidInd: Int
16 |
17 | # Total Tests Conducted
18 | testsConducted: Int
19 |
20 | # Total Remaining Test Kits
21 | remainingTests: Int
22 | }
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/PHLocations/PHLocations.ts:
--------------------------------------------------------------------------------
1 | import { DataSource } from 'apollo-datasource';
2 | import phLocations from 'ph-locations';
3 |
4 | const { psgc } = phLocations;
5 |
6 | class PHLocations extends DataSource {
7 | getRegions(): PSGCRegion[] {
8 | return psgc.regions;
9 | }
10 |
11 | getProvinces(): PSGCProvince[] {
12 | return psgc.provinces;
13 | }
14 |
15 | getCities(): PSGCCitiesMunicipality[] {
16 | return psgc.citiesMunicipalities;
17 | }
18 | }
19 |
20 | export default PHLocations;
21 |
--------------------------------------------------------------------------------
/hosting/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grommet, Box } from "grommet";
3 | import Main from "./Main"
4 | import './main.css';
5 |
6 | const theme = {
7 | global: {
8 | font: {
9 | family: "Roboto",
10 | size: "18px",
11 | height: "20px"
12 | }
13 | }
14 | };
15 |
16 | function App() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/functions/src/utils/toRegion.ts:
--------------------------------------------------------------------------------
1 | import { psgc } from 'ph-locations';
2 |
3 | const { regions } = psgc;
4 |
5 | export default (region: string | null): string | null => {
6 | if (!region) {
7 | return null;
8 | }
9 |
10 | if (region.toUpperCase() === 'REPATRIATE') {
11 | return null;
12 | }
13 |
14 | const match = regions.find((r) => region.includes(r.name) || region.includes(r.altName));
15 |
16 | if (!match) {
17 | throw new Error(`cannot match ${region}`);
18 | }
19 |
20 | return (match && match.name) || null;
21 | };
22 |
--------------------------------------------------------------------------------
/hosting/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/functions/src/utils/toDate.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export const INPUT_FORMAT_CASE_INFORMATION = 'YYYY-MM-DD';
4 | export const INPUT_FORMAT_TESTING_AGGREGATES = 'MMMM D, YYYY';
5 | export const INPUT_FORMAT_WEEKLY_REPORT = 'YYYY-MM-DD';
6 | export const INPUT_FORMAT_DAILY_REPORT = 'YYYY-MM-DD';
7 | export const OUTPUT_FORMAT = 'YYYY-MM-DD';
8 |
9 | export default (str: string, format: string): string | null => {
10 | if (!moment(str, format, true).isValid()) {
11 | return null;
12 | }
13 |
14 | return moment(str, format, true).format(OUTPUT_FORMAT);
15 | };
16 |
--------------------------------------------------------------------------------
/functions/src/types/HealthFacilityCapacityReport.ts:
--------------------------------------------------------------------------------
1 | import HealthFacility from './HealthFacility';
2 |
3 | interface HealthFacilityCapacityReport {
4 | healthFacility: HealthFacility;
5 |
6 | dateReported: string;
7 |
8 | icuVacant: number;
9 |
10 | icuOccupied: number;
11 |
12 | isolationBedsVacant: number;
13 |
14 | isolationBedsOccupied: number;
15 |
16 | wardBedsVacant: number;
17 |
18 | wardBedsOccupied: number;
19 |
20 | mechVentilatorsVacant: number;
21 |
22 | mechVentilatorsOccupied: number;
23 | }
24 |
25 | export default HealthFacilityCapacityReport;
26 |
--------------------------------------------------------------------------------
/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "emitDecoratorMetadata": true,
4 | "esModuleInterop": true,
5 | "lib": [
6 | "esnext",
7 | "esnext.asynciterable"
8 | ],
9 | "declaration": false,
10 | "experimentalDecorators": true,
11 | "module": "commonjs",
12 | "moduleResolution": "node",
13 | "noImplicitReturns": true,
14 | "noUnusedLocals": true,
15 | "outDir": "dist",
16 | "rootDir": "src",
17 | "sourceMap": true,
18 | "target": "es2017"
19 | },
20 | "compileOnSave": true,
21 | "include": [
22 | "src"
23 | ]
24 | }
--------------------------------------------------------------------------------
/functions/src/graphql/typeDefs/PPEReport.graphql:
--------------------------------------------------------------------------------
1 | type PPEReport {
2 | # Health Facility
3 | healthFacility: HealthFacility!
4 |
5 | # Date Reported
6 | dateReported: Date!
7 |
8 | # Gowns
9 | gowns: Int!
10 |
11 | # Gloves
12 | gloves: Int!
13 |
14 | # Head Covers
15 | headCovers: Int!
16 |
17 | # Goggles
18 | goggles: Int!
19 |
20 | # Coveralls
21 | coveralls: Int!
22 |
23 | # Shoe Covers
24 | shoeCovers: Int!
25 |
26 | # Face Shields
27 | faceShields: Int!
28 |
29 | # Surgical Masks
30 | surgicalMasks: Int!
31 |
32 | # N95 Masks
33 | n95Masks: Int!
34 | }
--------------------------------------------------------------------------------
/hosting/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/functions/src/firebase.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin';
2 | import { config } from 'firebase-functions';
3 |
4 | if (config().runtime && config().runtime.env === 'production') {
5 | admin.initializeApp();
6 | } else {
7 | // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
8 | const serviceAccount = require('../../serviceAccountKey.json');
9 | const initConfig = {
10 | credential: admin.credential.cert(serviceAccount),
11 | databaseURL: 'https://ncovidtracker-api.firebaseio.com',
12 | };
13 | admin.initializeApp(initConfig);
14 | }
15 |
16 | export default admin;
17 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/queries/LocationQuery.ts:
--------------------------------------------------------------------------------
1 | import { DataSources } from '../../dataSources';
2 |
3 | export interface Args {
4 | dataSources: DataSources;
5 | }
6 |
7 | export default {
8 | regions: (
9 | _obj: unknown,
10 | { dataSources }: Args,
11 | ): PSGCRegion[] => dataSources
12 | .phLocations
13 | .getRegions(),
14 |
15 | provinces: (
16 | _obj: unknown,
17 | { dataSources }: Args,
18 | ): PSGCProvince[] => dataSources
19 | .phLocations
20 | .getProvinces(),
21 |
22 | cities: (
23 | _obj: unknown,
24 | { dataSources }: Args,
25 | ): PSGCCitiesMunicipality[] => dataSources
26 | .phLocations
27 | .getCities(),
28 | };
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Hyubs Ursua
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6 |
7 |
--------------------------------------------------------------------------------
/functions/src/apollo.ts:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from 'apollo-server-express';
2 | import environment from './environment';
3 | import dataSources from './graphql/dataSources';
4 | import mocks from './graphql/mocks';
5 | import resolvers from './graphql/resolvers';
6 | import typeDefs from './graphql/typeDefs';
7 |
8 | if (environment.graphql.printTypeDef) {
9 | // eslint-disable-next-line no-console
10 | console.log(typeDefs, '\n');
11 | }
12 |
13 | const server = new ApolloServer({
14 | resolvers,
15 | typeDefs,
16 | dataSources,
17 | introspection: environment.apollo.introspection,
18 | playground: environment.apollo.playground,
19 | mocks: environment.apollo.mocks && mocks,
20 | });
21 |
22 | export default server;
23 |
--------------------------------------------------------------------------------
/functions/src/types/AgeGroup.ts:
--------------------------------------------------------------------------------
1 | enum AgeGroup {
2 | AGE_GROUP_0_TO_4 = '0 to 4',
3 | AGE_GROUP_5_TO_9 = '5 to 9',
4 | AGE_GROUP_10_TO_14 = '10 to 14',
5 | AGE_GROUP_15_TO_19 = '15 to 19',
6 | AGE_GROUP_20_TO_24 = '20 to 24',
7 | AGE_GROUP_25_TO_29 = '25 to 29',
8 | AGE_GROUP_30_TO_34 = '30 to 34',
9 | AGE_GROUP_35_TO_39 = '35 to 39',
10 | AGE_GROUP_40_TO_44 = '40 to 44',
11 | AGE_GROUP_45_TO_49 = '45 to 49',
12 | AGE_GROUP_50_TO_54 = '50 to 54',
13 | AGE_GROUP_55_TO_59 = '55 to 59',
14 | AGE_GROUP_60_TO_64 = '60 to 64',
15 | AGE_GROUP_65_TO_69 = '65 to 69',
16 | AGE_GROUP_70_TO_74 = '70 to 74',
17 | AGE_GROUP_75_TO_79 = '75 to 79',
18 | AGE_GROUP_80_AND_ABOVE = '80+',
19 | }
20 |
21 | export default AgeGroup;
22 |
--------------------------------------------------------------------------------
/functions/src/types/TestingAggregate.ts:
--------------------------------------------------------------------------------
1 | import TestingFacility from './TestingFacility';
2 |
3 | interface TestingAggregate {
4 | date: string;
5 | facility: TestingFacility;
6 | dailyOutput: {
7 | positiveInd: number;
8 | uniqueIndTested: number;
9 | testsConducted: number;
10 | };
11 | totals: {
12 | uniqueIndTested: number;
13 | positiveInd: number;
14 | negativeInd: number;
15 | equivocalInd: number;
16 | invalidInd: number;
17 | testsConducted: number;
18 | remainingTests: number;
19 | };
20 | ratioUniqueInd: {
21 | positiveInd: number;
22 | negativeInd: number;
23 | equivocalInd: number;
24 | invalidInd: number;
25 | };
26 | }
27 |
28 | export default TestingAggregate;
29 |
--------------------------------------------------------------------------------
/functions/src/utils/toProvince.ts:
--------------------------------------------------------------------------------
1 | import { psgc } from 'ph-locations';
2 |
3 | const { provinces } = psgc;
4 |
5 | export default (province: string | null): string | null => {
6 | if (!province) {
7 | return null;
8 | }
9 |
10 | if (province === 'NCR') {
11 | return 'Metro Manila';
12 | }
13 |
14 | const provinceNoSuffix = province.replace(' PROVINCE', '');
15 |
16 | const match = provinces.find((p) => (
17 | provinceNoSuffix === p.name.toUpperCase()
18 | || (provinceNoSuffix === 'COMPOSTELA VALLEY' && p.name === 'Davao De Oro')
19 | ));
20 |
21 | if (!match && province !== 'For Validation') {
22 | throw new Error(`cannot match ${province}`);
23 | }
24 |
25 | return (match && match.name) || null;
26 | };
27 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/index.ts:
--------------------------------------------------------------------------------
1 | import QuarantineStatus from './enums/QuarantineStatus';
2 | import RemovalType from './enums/RemovalType';
3 | import Sex from './enums/Sex';
4 | import CasesQuery from './queries/CasesQuery';
5 | import HealthFacilityCapacityQuery from './queries/HealthFacilityCapacityQuery';
6 | import PPEQuery from './queries/PPEQuery';
7 | import RatioUniqueIndQuery from './queries/RatioUniqueIndQuery';
8 | import TestingQuery from './queries/TestingQuery';
9 | import Query from './Query';
10 | import Date from './scalars/Date';
11 |
12 | export default {
13 | RemovalType,
14 | QuarantineStatus,
15 | CasesQuery,
16 | Date,
17 | Query,
18 | RatioUniqueIndQuery,
19 | Sex,
20 | TestingQuery,
21 | PPEQuery,
22 | HealthFacilityCapacityQuery,
23 | };
24 |
--------------------------------------------------------------------------------
/functions/src/graphql/resolvers/Query.ts:
--------------------------------------------------------------------------------
1 | import CasesQuery from './queries/CasesQuery';
2 | import HealthFacilityCapacityQuery from './queries/HealthFacilityCapacityQuery';
3 | import MetadataQuery from './queries/MetadataQuery';
4 | import PPEQuery from './queries/PPEQuery';
5 | import TestingQuery from './queries/TestingQuery';
6 |
7 | export default {
8 | hello(): string {
9 | return 'Hello ncovph';
10 | },
11 |
12 | cases(): object {
13 | return CasesQuery;
14 | },
15 |
16 | testing(): object {
17 | return TestingQuery;
18 | },
19 |
20 | ppe(): object {
21 | return PPEQuery;
22 | },
23 |
24 | healthFacilityCapacity(): object {
25 | return HealthFacilityCapacityQuery;
26 | },
27 |
28 | metadata(): object {
29 | return MetadataQuery;
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/functions/src/express.ts:
--------------------------------------------------------------------------------
1 | import bodyParser from 'body-parser';
2 | import express from 'express';
3 | import apollo from './apollo';
4 | import middlewares from './middlewares';
5 |
6 | const app = express();
7 | app.use(middlewares.cors.handler);
8 | app.use(bodyParser.json());
9 |
10 | if (process.env.NODE_ENV !== 'development') {
11 | app.use(middlewares.rateLimit.handler);
12 | }
13 |
14 | app.use((req, _res, next) => {
15 | if (req.body && req.body.operationName === null) {
16 | // eslint-disable-next-line no-console
17 | console.log(
18 | `GraphQL query: ${JSON.stringify(req.body.query, null, 2)
19 | .replace(/\\n/g, '')
20 | .replace(/\s+/g, ' ')}`,
21 | );
22 | }
23 |
24 | next();
25 | });
26 |
27 | apollo.applyMiddleware({ app, path: '/' });
28 |
29 | export default app;
30 |
--------------------------------------------------------------------------------
/functions/src/types/CaseInformation.ts:
--------------------------------------------------------------------------------
1 | import HealthStatus from './HealthStatus';
2 | import PregnancyStatus from './PregnancyStatus';
3 | import QuarantineStatus from './QuarantineStatus';
4 | import RemovalType from './RemovalType';
5 | import Sex from './Sex';
6 |
7 | interface CaseInformation {
8 | caseNumber: string;
9 | age: number;
10 | sex: Sex;
11 | dateReportConfirmed: string;
12 | dateRecovery?: string;
13 | dateDeath?: string;
14 | removalType?: RemovalType;
15 | dateReportRemoved: string;
16 | admitted: boolean;
17 | residence: {
18 | region?: string;
19 | province?: string;
20 | city?: string;
21 | };
22 | healthStatus: HealthStatus;
23 | didHomeQuarantine: QuarantineStatus;
24 | dateOnsetSymptoms: string;
25 | isPregnant: PregnancyStatus;
26 | }
27 |
28 | export default CaseInformation;
29 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropWeeklyReport/DataDropWeeklyReport.ts:
--------------------------------------------------------------------------------
1 | import { DataSource } from 'apollo-datasource';
2 | import PPEReport from '../../../types/PPEReport';
3 | import ppeReports from './PPEReports';
4 |
5 | class DataDropWeeklyReport extends DataSource {
6 | getLatest(): PPEReport[] {
7 | const keyedReports = {};
8 |
9 | ppeReports.forEach((ppeReport): void => {
10 | if (keyedReports[ppeReport.healthFacility.code]) {
11 | const current: PPEReport = keyedReports[ppeReport.healthFacility.code];
12 |
13 | if (ppeReport.dateReported <= current.dateReported) {
14 | return;
15 | }
16 | }
17 |
18 | keyedReports[ppeReport.healthFacility.code] = ppeReport;
19 | });
20 |
21 | return Object.values(keyedReports);
22 | }
23 | }
24 |
25 | export default DataDropWeeklyReport;
26 |
--------------------------------------------------------------------------------
/hosting/src/sections/DataSource.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, Paragraph } from "grommet";
3 | import SectionTitle from "../components/SectionTitle";
4 | import ExternalLink from "../components/ExternalLink";
5 |
6 | export default function DataSource(): JSX.Element {
7 | return (
8 |
9 |
10 |
11 | The data is uploaded manually from official sources daily as early as 5pm (sometimes late). Data comes from the following official sources:
12 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "emulators": {
3 | "functions": {
4 | "port": "4000"
5 | }
6 | },
7 | "functions": {
8 | "predeploy": [
9 | "npm --prefix \"$RESOURCE_DIR\" run lint",
10 | "npm --prefix \"$RESOURCE_DIR\" run build:clean",
11 | "npm --prefix \"$RESOURCE_DIR\" run copy:graphql",
12 | "npm --prefix \"$RESOURCE_DIR\" run copy:data"
13 | ],
14 | "source": "functions"
15 | },
16 | "hosting": {
17 | "trailingSlash": false,
18 | "predeploy": [
19 | "npm --prefix hosting run build"
20 | ],
21 | "public": "hosting/build",
22 | "ignore": [
23 | "**/*.log",
24 | "**/.*",
25 | "**/node_modules/**",
26 | "firebase.json"
27 | ],
28 | "rewrites": [
29 | {
30 | "source": "/graphql{,/**/*}",
31 | "function": "apollo"
32 | }
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/functions/src/utils/toHealthStatus.ts:
--------------------------------------------------------------------------------
1 | import HealthStatus from '../types/HealthStatus';
2 | import toNullableString from './toNullableString';
3 |
4 | export default (str: string): HealthStatus => {
5 | const val = toNullableString(str);
6 |
7 | if (!val) {
8 | return HealthStatus.UNKNOWN;
9 | }
10 |
11 | switch (val.toUpperCase()) {
12 | case 'ASYMPTOMATIC':
13 | return HealthStatus.ASYMPTOMATIC;
14 | break;
15 | case 'MILD':
16 | return HealthStatus.MILD;
17 | break;
18 | case 'SEVERE':
19 | return HealthStatus.SEVERE;
20 | break;
21 | case 'CRITICAL':
22 | return HealthStatus.CRITICAL;
23 | break;
24 | case 'RECOVERED':
25 | return HealthStatus.RECOVERED;
26 | break;
27 | case 'DIED':
28 | return HealthStatus.DIED;
29 | break;
30 | default:
31 | return HealthStatus.DIED;
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/hosting/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ncovph",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropWeeklyReport/PPEReports.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import Papa from 'papaparse';
4 | import PPEReport from '../../../types/PPEReport';
5 | import toPPEReport from './toPPEReport';
6 |
7 | const csvPath = path.resolve(__dirname, '../../../data/WeeklyReport.csv');
8 |
9 | if (!fs.existsSync(csvPath)) {
10 | throw new Error(`File not found in ${csvPath}`);
11 | }
12 |
13 | const csvStr = fs.readFileSync(csvPath, 'utf8').trim();
14 | const ppeReports: PPEReport[] = Papa.parse(csvStr)
15 | .data
16 | .slice(1)
17 | .map((d: string[]): PPEReport => toPPEReport(d))
18 | .sort((a: PPEReport, b: PPEReport): number => {
19 | if (a.dateReported < b.dateReported) {
20 | return -1;
21 | }
22 |
23 | if (a.dateReported > b.dateReported) {
24 | return 1;
25 | }
26 |
27 | return 0;
28 | });
29 |
30 | export default ppeReports;
31 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 28
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: >
18 | This issue has been automatically closed because it was stale for a long time.
19 | If you think this is still relevant or needs to be urgently resolved, kindly
20 | comment in this issue. Thanks again!
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropTestingAggregates/TestingAggregates.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import Papa from 'papaparse';
4 | import TestingAggregate from '../../../types/TestingAggregate';
5 | import toTestingAggregates from './toTestingAggregates';
6 |
7 | const csvPath = path.resolve(__dirname, '../../../data/TestingAggregates.csv');
8 |
9 | if (!fs.existsSync(csvPath)) {
10 | throw new Error(`File not found in ${csvPath}`);
11 | }
12 |
13 | const csvStr = fs.readFileSync(csvPath, 'utf8').trim();
14 | const tests: TestingAggregate[] = Papa.parse(csvStr)
15 | .data
16 | .slice(1)
17 | .map((d: string[]): TestingAggregate => toTestingAggregates(d))
18 | .sort((a: TestingAggregate, b: TestingAggregate): number => {
19 | if (a.date < b.date) {
20 | return -1;
21 | }
22 |
23 | if (a.date > b.date) {
24 | return 1;
25 | }
26 |
27 | return 0;
28 | });
29 |
30 | export default tests;
31 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/DataDropCaseInformation/Cases.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import Papa from 'papaparse';
4 | import CaseInformation from '../../../types/CaseInformation';
5 | import toCaseInformation from './toCaseInformation';
6 |
7 | const csvPath = path.resolve(__dirname, '../../../data/CaseInformation.csv');
8 |
9 | if (!fs.existsSync(csvPath)) {
10 | throw new Error(`File not found in ${csvPath}`);
11 | }
12 |
13 | const csvStr = fs.readFileSync(csvPath, 'utf8').trim();
14 | const cases: CaseInformation[] = Papa.parse(csvStr)
15 | .data
16 | .slice(1)
17 | .map((d: string[]): CaseInformation => toCaseInformation(d))
18 | .sort((a: CaseInformation, b: CaseInformation): number => {
19 | if (a.dateReportConfirmed < b.dateReportConfirmed) {
20 | return -1;
21 | }
22 |
23 | if (a.dateReportConfirmed > b.dateReportConfirmed) {
24 | return 1;
25 | }
26 |
27 | return 0;
28 | });
29 |
30 | export default cases;
31 |
--------------------------------------------------------------------------------
/functions/src/graphql/dataSources/index.ts:
--------------------------------------------------------------------------------
1 | import { DataSources as GraphQLDataSources } from 'apollo-server-core/src/graphqlOptions';
2 | import DataDropCaseInformation from './DataDropCaseInformation';
3 | import DataDropDailyReport from './DataDropDailyReport';
4 | import DataDropTestingAggregates from './DataDropTestingAggregates';
5 | import DataDropWeeklyReport from './DataDropWeeklyReport';
6 | import PHLocations from './PHLocations';
7 |
8 | export interface DataSources extends GraphQLDataSources