@common-grants/core 0.1.0-alpha.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/lib/api.tsp +23 -6
- package/lib/core/fields/address.tsp +86 -0
- package/lib/core/fields/custom-field.tsp +6 -5
- package/lib/core/fields/email.tsp +35 -0
- package/lib/core/fields/event.tsp +136 -14
- package/lib/core/fields/index.tsp +8 -0
- package/lib/core/fields/money.tsp +2 -2
- package/lib/core/fields/name.tsp +30 -0
- package/lib/core/fields/pcs.tsp +112 -0
- package/lib/core/fields/phone.tsp +65 -0
- package/lib/core/filters/base.tsp +83 -0
- package/lib/core/filters/date.tsp +38 -0
- package/lib/core/filters/index.tsp +40 -0
- package/lib/core/filters/money.tsp +38 -0
- package/lib/core/filters/numeric.tsp +50 -0
- package/lib/core/filters/string.tsp +31 -0
- package/lib/core/models/application.tsp +142 -0
- package/lib/core/models/index.tsp +4 -1
- package/lib/core/models/opportunity/base.tsp +8 -7
- package/lib/core/models/opportunity/funding.tsp +10 -5
- package/lib/core/models/opportunity/index.tsp +1 -0
- package/lib/core/models/opportunity/search.tsp +102 -0
- package/lib/core/models/opportunity/status.tsp +0 -3
- package/lib/core/models/opportunity/timeline.tsp +19 -10
- package/lib/core/models/organization.tsp +105 -0
- package/lib/core/models/person.tsp +43 -0
- package/lib/core/pagination.tsp +57 -0
- package/lib/core/responses/index.tsp +3 -0
- package/lib/core/responses/success.tsp +83 -28
- package/lib/core/routes/opportunities.tsp +45 -5
- package/lib/core/sorting.tsp +75 -0
- package/lib/core/types.tsp +48 -7
- package/package.json +9 -12
- package/lib/core/routes/utils.tsp +0 -19
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ namespace CustomAPI.CustomModels;
|
|
|
45
45
|
// Define a custom field
|
|
46
46
|
model Agency extends CustomField {
|
|
47
47
|
name: "Agency";
|
|
48
|
-
|
|
48
|
+
fieldType: CustomFieldType.string;
|
|
49
49
|
|
|
50
50
|
@example("Department of Transportation")
|
|
51
51
|
value: string;
|
|
@@ -76,13 +76,14 @@ using CommonGrants.Routes;
|
|
|
76
76
|
using TypeSpec.Http;
|
|
77
77
|
|
|
78
78
|
@tag("Search")
|
|
79
|
-
@route("/opportunities")
|
|
79
|
+
@route("/common-grants/opportunities")
|
|
80
80
|
namespace CustomAPI.CustomRoutes {
|
|
81
81
|
alias OpportunitiesRouter = Opportunities;
|
|
82
82
|
|
|
83
|
-
// Use the default model for list but custom model for read
|
|
83
|
+
// Use the default model for list but custom model for read and search
|
|
84
84
|
op list is OpportunitiesRouter.list;
|
|
85
85
|
op read is OpportunitiesRouter.read<CustomModels.CustomOpportunity>;
|
|
86
|
+
op search is OpportunitiesRouter.search<CustomModels.CustomOpportunity>;
|
|
86
87
|
}
|
|
87
88
|
```
|
|
88
89
|
|
|
@@ -100,9 +101,7 @@ import "./routes.tsp"; // Import the routes from above
|
|
|
100
101
|
using TypeSpec.Http;
|
|
101
102
|
|
|
102
103
|
/** Description of your API goes here */
|
|
103
|
-
@service({
|
|
104
|
-
title: "Custom API",
|
|
105
|
-
})
|
|
104
|
+
@service(#{ title: "Custom API" })
|
|
106
105
|
namespace CustomAPI;
|
|
107
106
|
```
|
|
108
107
|
|
package/lib/api.tsp
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
// Import Schemas.and Routes to make them available outside the package
|
|
2
2
|
import "./core/index.tsp";
|
|
3
3
|
import "@typespec/http";
|
|
4
|
+
import "@typespec/openapi";
|
|
4
5
|
|
|
5
6
|
using TypeSpec.Http;
|
|
7
|
+
using TypeSpec.OpenAPI;
|
|
6
8
|
|
|
7
9
|
/** The base OpenAPI specification for a CommonGrants API
|
|
8
10
|
*
|
|
9
11
|
* In order for an API to be "compliant" with the CommonGrants protocol,
|
|
10
12
|
* it must implement all of the routes with the "required" tag in this specification.
|
|
11
13
|
*/
|
|
12
|
-
@service({
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
@service(#{ title: "CommonGrants Base API" })
|
|
15
|
+
@tagMetadata(
|
|
16
|
+
"optional",
|
|
17
|
+
#{ description: "Endpoints that MAY be implemented by CommonGrants APIs" }
|
|
18
|
+
)
|
|
19
|
+
@tagMetadata(
|
|
20
|
+
"required",
|
|
21
|
+
#{
|
|
22
|
+
description: "Endpoints that MUST be implemented by all CommonGrants APIs",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
@tagMetadata(
|
|
26
|
+
"Opportunities",
|
|
27
|
+
#{ description: "Endpoints related to funding opportunities" }
|
|
28
|
+
)
|
|
15
29
|
namespace CommonGrants.API;
|
|
16
30
|
|
|
17
31
|
@tag("Opportunities")
|
|
18
|
-
@route("/opportunities")
|
|
32
|
+
@route("/common-grants/opportunities")
|
|
19
33
|
namespace Opportunities {
|
|
20
34
|
alias Router = Routes.Opportunities;
|
|
21
35
|
|
|
22
36
|
@tag("required")
|
|
23
|
-
op
|
|
37
|
+
op list is Router.list;
|
|
24
38
|
|
|
25
39
|
@tag("required")
|
|
26
|
-
op
|
|
40
|
+
op read is Router.read;
|
|
41
|
+
|
|
42
|
+
@tag("optional")
|
|
43
|
+
op search is Router.search;
|
|
27
44
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A mailing address. */
|
|
4
|
+
@example(Examples.Address.apartment)
|
|
5
|
+
model Address {
|
|
6
|
+
/** The primary street address line. */
|
|
7
|
+
street1: string;
|
|
8
|
+
|
|
9
|
+
/** Additional street address information (e.g., apartment number, suite, etc.). */
|
|
10
|
+
street2?: string;
|
|
11
|
+
|
|
12
|
+
/** The city or municipality name. */
|
|
13
|
+
city: string;
|
|
14
|
+
|
|
15
|
+
/** The state, province, or region name. */
|
|
16
|
+
stateOrProvince: string;
|
|
17
|
+
|
|
18
|
+
/** The country name or ISO country code. */
|
|
19
|
+
country: string;
|
|
20
|
+
|
|
21
|
+
/** The postal or ZIP code for the address. */
|
|
22
|
+
postalCode: string;
|
|
23
|
+
|
|
24
|
+
/** The latitude coordinate of the address location. */
|
|
25
|
+
latitude?: numeric;
|
|
26
|
+
|
|
27
|
+
/** The longitude coordinate of the address location. */
|
|
28
|
+
longitude?: numeric;
|
|
29
|
+
|
|
30
|
+
/** Additional geospatial data in GeoJSON format. */
|
|
31
|
+
geography?: Record<unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A collection of addresses. */
|
|
35
|
+
@example(Examples.Address.personalCollection)
|
|
36
|
+
@example(Examples.Address.orgCollection)
|
|
37
|
+
model AddressCollection {
|
|
38
|
+
/** The primary address for a person or organization. */
|
|
39
|
+
primary: Address;
|
|
40
|
+
|
|
41
|
+
/** Additional addresses keyed by a descriptive label (e.g., "work", "home", "international"). */
|
|
42
|
+
otherAddresses?: Record<Address>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
namespace Examples.Address {
|
|
46
|
+
const apartment = #{
|
|
47
|
+
street1: "123 Main St",
|
|
48
|
+
city: "Anytown",
|
|
49
|
+
stateOrProvince: "CA",
|
|
50
|
+
country: "US",
|
|
51
|
+
postalCode: "12345",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const orgAddress = #{
|
|
55
|
+
street1: "456 Main St",
|
|
56
|
+
street2: "Suite 100",
|
|
57
|
+
city: "Anytown",
|
|
58
|
+
stateOrProvince: "CA",
|
|
59
|
+
country: "US",
|
|
60
|
+
postalCode: "12345",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const international = #{
|
|
64
|
+
street1: "123 Rue Principale",
|
|
65
|
+
city: "Montreal",
|
|
66
|
+
stateOrProvince: "QC",
|
|
67
|
+
country: "CA",
|
|
68
|
+
postalCode: "H2Y 1C6",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const personalCollection = #{
|
|
72
|
+
primary: Examples.Address.apartment,
|
|
73
|
+
otherAddresses: #{
|
|
74
|
+
work: Examples.Address.apartment,
|
|
75
|
+
home: Examples.Address.apartment,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const orgCollection = #{
|
|
80
|
+
primary: Examples.Address.orgAddress,
|
|
81
|
+
otherAddresses: #{
|
|
82
|
+
satellite: Examples.Address.orgAddress,
|
|
83
|
+
international: Examples.Address.international,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -8,6 +8,7 @@ namespace CommonGrants.Fields;
|
|
|
8
8
|
enum CustomFieldType {
|
|
9
9
|
string,
|
|
10
10
|
number,
|
|
11
|
+
integer,
|
|
11
12
|
boolean,
|
|
12
13
|
object,
|
|
13
14
|
array,
|
|
@@ -25,7 +26,7 @@ enum CustomFieldType {
|
|
|
25
26
|
* model Agency extends CustomField {
|
|
26
27
|
* name: "agency";
|
|
27
28
|
*
|
|
28
|
-
*
|
|
29
|
+
* fieldType: CustomFieldType.string;
|
|
29
30
|
*
|
|
30
31
|
* @example("Department of Transportation")
|
|
31
32
|
* value: string;
|
|
@@ -42,13 +43,13 @@ enum CustomFieldType {
|
|
|
42
43
|
Examples.CustomField.eligibilityTypes,
|
|
43
44
|
#{ title: "Array field for eligibility types" }
|
|
44
45
|
)
|
|
45
|
-
@doc("A custom field on a model")
|
|
46
|
+
@doc("A custom field on a model")
|
|
46
47
|
model CustomField {
|
|
47
48
|
/** Name of the custom field */
|
|
48
49
|
name: string;
|
|
49
50
|
|
|
50
51
|
/** The JSON schema type to use when de-serializing the `value` field */
|
|
51
|
-
|
|
52
|
+
fieldType: CustomFieldType;
|
|
52
53
|
|
|
53
54
|
/** Link to the full JSON schema for this custom field */
|
|
54
55
|
schema?: url;
|
|
@@ -69,7 +70,7 @@ namespace Examples.CustomField {
|
|
|
69
70
|
/** An example of a string custom field */
|
|
70
71
|
const programArea = #{
|
|
71
72
|
name: "programArea",
|
|
72
|
-
|
|
73
|
+
fieldType: CustomFieldType.string,
|
|
73
74
|
value: "Healthcare Innovation",
|
|
74
75
|
description: "Primary focus area of the grant program",
|
|
75
76
|
schema: "https://example.com/program-areas.json",
|
|
@@ -78,7 +79,7 @@ namespace Examples.CustomField {
|
|
|
78
79
|
/** An example of an array custom field */
|
|
79
80
|
const eligibilityTypes = #{
|
|
80
81
|
name: "eligibilityType",
|
|
81
|
-
|
|
82
|
+
fieldType: CustomFieldType.array,
|
|
82
83
|
value: #["nonprofit", "academic"],
|
|
83
84
|
description: "Types of eligible organizations",
|
|
84
85
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "../types.tsp";
|
|
2
|
+
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
|
+
|
|
5
|
+
/** An email address. */
|
|
6
|
+
alias Email = Types.email; // Exposes Email from CommonGrants.Fields
|
|
7
|
+
|
|
8
|
+
/** A collection of email addresses. */
|
|
9
|
+
@example(Examples.Email.personalCollection)
|
|
10
|
+
model EmailCollection {
|
|
11
|
+
/** The primary email address for a person or organization. */
|
|
12
|
+
primary: Types.email;
|
|
13
|
+
|
|
14
|
+
/** Additional email addresses keyed by a descriptive label (e.g., "work", "personal", "support"). */
|
|
15
|
+
otherEmails?: Record<Types.email>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
namespace Examples.Email {
|
|
19
|
+
const personalCollection = #{
|
|
20
|
+
primary: "john.doe@example.com",
|
|
21
|
+
otherEmails: #{
|
|
22
|
+
work: "john.doe@work.com",
|
|
23
|
+
personal: "john.doe@gmail.com",
|
|
24
|
+
school: "john.doe@school.edu",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const orgCollection = #{
|
|
29
|
+
primary: "info@example.com",
|
|
30
|
+
otherEmails: #{
|
|
31
|
+
support: "support@example.com",
|
|
32
|
+
marketing: "marketing@example.com",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -2,26 +2,119 @@ import "../types.tsp";
|
|
|
2
2
|
|
|
3
3
|
namespace CommonGrants.Fields;
|
|
4
4
|
|
|
5
|
-
using
|
|
5
|
+
using Types;
|
|
6
6
|
|
|
7
7
|
// ########################################
|
|
8
|
-
//
|
|
8
|
+
// Event type
|
|
9
9
|
// ########################################
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** Type of event (e.g., a single date, a date range, or a custom event)
|
|
12
|
+
*
|
|
13
|
+
* - singleDate: A single date (and possible time)
|
|
14
|
+
* - dateRange: A period of time with a start and end date
|
|
15
|
+
* - other: Other event type (e.g., a recurring event)
|
|
16
|
+
*/
|
|
17
|
+
enum EventType {
|
|
18
|
+
/** A single date with no time */
|
|
19
|
+
singleDate,
|
|
20
|
+
|
|
21
|
+
/** A range of dates with no time */
|
|
22
|
+
dateRange,
|
|
23
|
+
|
|
24
|
+
/** Other event type */
|
|
25
|
+
other,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ########################################
|
|
29
|
+
// Event
|
|
30
|
+
// ########################################
|
|
31
|
+
|
|
32
|
+
/** Union of all event types */
|
|
33
|
+
union Event {
|
|
34
|
+
/** A single date event */
|
|
35
|
+
singleDate: SingleDateEvent,
|
|
36
|
+
|
|
37
|
+
/** A date range event */
|
|
38
|
+
dateRange: DateRangeEvent,
|
|
39
|
+
|
|
40
|
+
/** Other event type */
|
|
41
|
+
other: OtherEvent,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ########################################
|
|
45
|
+
// Event base
|
|
46
|
+
// ########################################
|
|
47
|
+
|
|
48
|
+
/** Base model for all events */
|
|
49
|
+
@discriminator("eventType")
|
|
50
|
+
model EventBase {
|
|
15
51
|
/** Human-readable name of the event (e.g., 'Application posted', 'Question deadline') */
|
|
16
52
|
name: string;
|
|
17
53
|
|
|
54
|
+
/** Type of event */
|
|
55
|
+
eventType: EventType;
|
|
56
|
+
|
|
57
|
+
/** Description of what this event represents */
|
|
58
|
+
description?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ########################################
|
|
62
|
+
// Single date event
|
|
63
|
+
// ########################################
|
|
64
|
+
|
|
65
|
+
/** Description of an event that has a date (and possible time) associated with it */
|
|
66
|
+
@example(Examples.Event.postedDate, #{ title: "Opportunity posted date" })
|
|
67
|
+
@example(Examples.Event.closeDate, #{ title: "Opportunity close date" })
|
|
68
|
+
model SingleDateEvent extends EventBase {
|
|
69
|
+
/** Type of event */
|
|
70
|
+
eventType: EventType.singleDate;
|
|
71
|
+
|
|
18
72
|
/** Date of the event in in ISO 8601 format: YYYY-MM-DD */
|
|
19
73
|
date: isoDate;
|
|
20
74
|
|
|
21
75
|
/** Time of the event in ISO 8601 format: HH:MM:SS */
|
|
22
76
|
time?: isoTime;
|
|
77
|
+
}
|
|
23
78
|
|
|
24
|
-
|
|
79
|
+
// ########################################
|
|
80
|
+
// Date range event
|
|
81
|
+
// ########################################
|
|
82
|
+
|
|
83
|
+
/** Description of an event that has a start and end date (and possible time) associated with it */
|
|
84
|
+
@example(Examples.Event.performancePeriod, #{ title: "Period of performance" })
|
|
85
|
+
@example(Examples.Event.applicationPeriod, #{ title: "Application period" })
|
|
86
|
+
model DateRangeEvent extends EventBase {
|
|
87
|
+
/** Type of event */
|
|
88
|
+
eventType: EventType.dateRange;
|
|
89
|
+
|
|
90
|
+
/** Start date of the event in ISO 8601 format: YYYY-MM-DD */
|
|
91
|
+
startDate: isoDate;
|
|
92
|
+
|
|
93
|
+
/** Start time of the event in ISO 8601 format: HH:MM:SS */
|
|
94
|
+
startTime?: isoTime;
|
|
95
|
+
|
|
96
|
+
/** End date of the event in ISO 8601 format: YYYY-MM-DD */
|
|
97
|
+
endDate: isoDate;
|
|
98
|
+
|
|
99
|
+
/** End time of the event in ISO 8601 format: HH:MM:SS */
|
|
100
|
+
endTime?: isoTime;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ########################################
|
|
104
|
+
// Custom event
|
|
105
|
+
// ########################################
|
|
106
|
+
|
|
107
|
+
/** Description of an event that is not a single date or date range */
|
|
108
|
+
@example(Examples.Event.infoSessions, #{ title: "Info sessions" })
|
|
109
|
+
model OtherEvent extends EventBase {
|
|
110
|
+
/** Type of event */
|
|
111
|
+
eventType: EventType.other;
|
|
112
|
+
|
|
113
|
+
/** Details of the event's timeline (e.g. "Every other Tuesday") */
|
|
114
|
+
details?: string;
|
|
115
|
+
|
|
116
|
+
/** Description of the event */
|
|
117
|
+
@example("Applications begin being accepted")
|
|
25
118
|
description?: string;
|
|
26
119
|
}
|
|
27
120
|
|
|
@@ -31,17 +124,46 @@ model Event {
|
|
|
31
124
|
|
|
32
125
|
namespace Examples.Event {
|
|
33
126
|
/** An example of a deadline event with a specific time */
|
|
34
|
-
const
|
|
35
|
-
name: "
|
|
127
|
+
const closeDate = #{
|
|
128
|
+
name: "Opportunity close date",
|
|
129
|
+
eventType: EventType.singleDate,
|
|
36
130
|
date: isoDate.fromISO("2024-12-31"),
|
|
37
131
|
time: isoTime.fromISO("17:00:00"),
|
|
38
|
-
description: "
|
|
132
|
+
description: "Opportunity closes for all applications",
|
|
39
133
|
};
|
|
40
134
|
|
|
41
|
-
/** An example of an
|
|
42
|
-
const
|
|
43
|
-
name: "
|
|
135
|
+
/** An example of an opportunity posted date without a specific time */
|
|
136
|
+
const postedDate = #{
|
|
137
|
+
name: "Application posted date",
|
|
138
|
+
eventType: EventType.singleDate,
|
|
44
139
|
date: isoDate.fromISO("2024-01-15"),
|
|
45
|
-
description: "
|
|
140
|
+
description: "Opportunity is posted publicly",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Period of application for the grant */
|
|
144
|
+
const applicationPeriod = #{
|
|
145
|
+
name: "Application period",
|
|
146
|
+
eventType: EventType.dateRange,
|
|
147
|
+
startDate: isoDate.fromISO("2024-01-01"),
|
|
148
|
+
endDate: isoDate.fromISO("2024-01-31"),
|
|
149
|
+
endTime: isoTime.fromISO("17:00:00"),
|
|
150
|
+
description: "Primary application period for the grant opportunity",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Period of performance for the grant */
|
|
154
|
+
const performancePeriod = #{
|
|
155
|
+
name: "Period of Performance",
|
|
156
|
+
eventType: EventType.dateRange,
|
|
157
|
+
startDate: isoDate.fromISO("2024-01-01"),
|
|
158
|
+
endDate: isoDate.fromISO("2024-12-31"),
|
|
159
|
+
description: "Period of performance for the grant",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Info sessions for the opportunity */
|
|
163
|
+
const infoSessions = #{
|
|
164
|
+
name: "Info sessions",
|
|
165
|
+
eventType: EventType.other,
|
|
166
|
+
details: "Every other Tuesday at 10:00 AM during the application period",
|
|
167
|
+
description: "Info sessions for the opportunity",
|
|
46
168
|
};
|
|
47
169
|
}
|
|
@@ -2,6 +2,13 @@ import "./custom-field.tsp";
|
|
|
2
2
|
import "./metadata.tsp";
|
|
3
3
|
import "./money.tsp";
|
|
4
4
|
import "./event.tsp";
|
|
5
|
+
import "./address.tsp";
|
|
6
|
+
import "./name.tsp";
|
|
7
|
+
import "./phone.tsp";
|
|
8
|
+
import "./pcs.tsp";
|
|
9
|
+
import "./email.tsp";
|
|
10
|
+
|
|
11
|
+
using TypeSpec.JsonSchema;
|
|
5
12
|
|
|
6
13
|
/** A standard set of fields, e.g. `money` that can be reused across models
|
|
7
14
|
*
|
|
@@ -17,4 +24,5 @@ import "./event.tsp";
|
|
|
17
24
|
* }
|
|
18
25
|
* ```
|
|
19
26
|
*/
|
|
27
|
+
@jsonSchema
|
|
20
28
|
namespace CommonGrants.Fields;
|
|
@@ -2,13 +2,13 @@ import "../types.tsp";
|
|
|
2
2
|
|
|
3
3
|
namespace CommonGrants.Fields;
|
|
4
4
|
|
|
5
|
-
using
|
|
5
|
+
using Types;
|
|
6
6
|
|
|
7
7
|
// ########################################
|
|
8
8
|
// Model definition
|
|
9
9
|
// ########################################
|
|
10
10
|
|
|
11
|
-
/** A monetary amount and the currency in which
|
|
11
|
+
/** A monetary amount and the currency in which it's denominated */
|
|
12
12
|
@example(Examples.Money.usdWithCents, #{ title: "US dollars and cents" })
|
|
13
13
|
@example(
|
|
14
14
|
Examples.Money.euroWithoutCents,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A person's name. */
|
|
4
|
+
@example(Examples.Name.janeDoe)
|
|
5
|
+
model Name {
|
|
6
|
+
/** Honorific prefix (e.g., Mr., Mrs., Dr., Prof.). */
|
|
7
|
+
prefix?: string;
|
|
8
|
+
|
|
9
|
+
/** The person's first or given name. */
|
|
10
|
+
firstName: string;
|
|
11
|
+
|
|
12
|
+
/** The person's middle name or names. */
|
|
13
|
+
middleName?: string;
|
|
14
|
+
|
|
15
|
+
/** The person's last name or family name. */
|
|
16
|
+
lastName: string;
|
|
17
|
+
|
|
18
|
+
/** Name suffix (e.g., Jr., Sr., III, Ph.D.). */
|
|
19
|
+
suffix?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
namespace Examples.Name {
|
|
23
|
+
const janeDoe = #{
|
|
24
|
+
prefix: "Dr.",
|
|
25
|
+
firstName: "Jane",
|
|
26
|
+
middleName: "Edward",
|
|
27
|
+
lastName: "Doe",
|
|
28
|
+
suffix: "Jr.",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A Philanthropy Classification System (PCS) term.
|
|
4
|
+
*
|
|
5
|
+
* The PCS is a hierarchical classification system for categorizing data related to
|
|
6
|
+
* philanthropic activities. It supports the following classes:
|
|
7
|
+
* - Organization types
|
|
8
|
+
* - Subjects
|
|
9
|
+
* - Population groups
|
|
10
|
+
* - Transaction types
|
|
11
|
+
* - Support strategies
|
|
12
|
+
*
|
|
13
|
+
* See https://taxonomy.candid.org/ for more information.
|
|
14
|
+
*/
|
|
15
|
+
@example(Examples.PCS.orgTypeTerm)
|
|
16
|
+
model PCSTerm {
|
|
17
|
+
/** The plain language PCS term. */
|
|
18
|
+
term: string;
|
|
19
|
+
|
|
20
|
+
/** The class to which the PCS term belongs. */
|
|
21
|
+
class: PCSClass;
|
|
22
|
+
|
|
23
|
+
/** The code for this PCS term. */
|
|
24
|
+
@pattern("^[A-Z]{2}[0-9]{6}$")
|
|
25
|
+
@example("UC000000")
|
|
26
|
+
code: string;
|
|
27
|
+
|
|
28
|
+
/** Description of the PCS term */
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// #########################################################
|
|
33
|
+
// PCSClass
|
|
34
|
+
// #########################################################
|
|
35
|
+
|
|
36
|
+
/** The class to which the PCS term belongs. */
|
|
37
|
+
enum PCSClass {
|
|
38
|
+
orgTypes: "Organization types",
|
|
39
|
+
subjects: "Subjects",
|
|
40
|
+
populationGroups: "Population groups",
|
|
41
|
+
transactionTypes: "Transaction types",
|
|
42
|
+
supportStrategies: "Support strategies",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// #########################################################
|
|
46
|
+
// PCS class types
|
|
47
|
+
// #########################################################
|
|
48
|
+
|
|
49
|
+
/** A Philanthropy Classification System (PCS) term for organization types.
|
|
50
|
+
*
|
|
51
|
+
* See https://taxonomy.candid.org/organization-type for more information.
|
|
52
|
+
*/
|
|
53
|
+
model PCSOrgType extends PCSTerm {
|
|
54
|
+
/** The PCS term for the organization type. */
|
|
55
|
+
class: PCSClass.orgTypes;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A Philanthropy Classification System (PCS) term for the subject of the grant.
|
|
59
|
+
*
|
|
60
|
+
* See https://taxonomy.candid.org/subjects for more information.
|
|
61
|
+
*/
|
|
62
|
+
model PCSSubject extends PCSTerm {
|
|
63
|
+
/** The PCS term for the subject. */
|
|
64
|
+
class: PCSClass.subjects;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A Philanthropy Classification System (PCS) term for populations served.
|
|
68
|
+
*
|
|
69
|
+
* See https://taxonomy.candid.org/populations for more information.
|
|
70
|
+
*/
|
|
71
|
+
model PCSPopulation extends PCSTerm {
|
|
72
|
+
/** The PCS term for the population. */
|
|
73
|
+
class: PCSClass.populationGroups;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A Philanthropy Classification System (PCS) term for support strategies.
|
|
77
|
+
*
|
|
78
|
+
* See https://taxonomy.candid.org/support-strategies for more information.
|
|
79
|
+
*/
|
|
80
|
+
model PCSSupportStrategy extends PCSTerm {
|
|
81
|
+
/** The PCS term for the support strategy. */
|
|
82
|
+
class: PCSClass.supportStrategies;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A Philanthropy Classification System (PCS) term for transaction types.
|
|
86
|
+
*
|
|
87
|
+
* See https://taxonomy.candid.org/transaction-types for more information.
|
|
88
|
+
*/
|
|
89
|
+
model PCSTransactionType extends PCSTerm {
|
|
90
|
+
/** The PCS term for the transaction type. */
|
|
91
|
+
class: PCSClass.transactionTypes;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// #########################################################
|
|
95
|
+
// Examples
|
|
96
|
+
// #########################################################
|
|
97
|
+
|
|
98
|
+
namespace Examples.PCS {
|
|
99
|
+
const orgTypeTerm = #{
|
|
100
|
+
term: "Hospital",
|
|
101
|
+
class: PCSClass.orgTypes,
|
|
102
|
+
description: "Institutions with the primary purpose of providing in-patient physical and mental health services...",
|
|
103
|
+
code: "EO000000",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const subjectTerm = #{
|
|
107
|
+
term: "Education",
|
|
108
|
+
class: PCSClass.subjects,
|
|
109
|
+
description: "All formally constituted educational institutions (except art and performing art schools) and projects or activities...",
|
|
110
|
+
code: "SB000000",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A phone number. */
|
|
4
|
+
@example(Examples.Phone.mobile)
|
|
5
|
+
@example(Examples.Phone.withExtension)
|
|
6
|
+
model Phone {
|
|
7
|
+
/** The international country code (e.g., "+1" for US/Canada). */
|
|
8
|
+
@pattern("^\\+[1-9][0-9]{1,3}$")
|
|
9
|
+
countryCode: string;
|
|
10
|
+
|
|
11
|
+
/** The local phone number without the country code. */
|
|
12
|
+
number: string;
|
|
13
|
+
|
|
14
|
+
/** Optional extension number for the phone line. */
|
|
15
|
+
extension?: string;
|
|
16
|
+
|
|
17
|
+
/** Indicates whether this is a mobile/cell phone number. */
|
|
18
|
+
isMobile?: boolean = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// #########################################################
|
|
22
|
+
// Phone Collection
|
|
23
|
+
// #########################################################
|
|
24
|
+
|
|
25
|
+
/** A collection of phone numbers for a person. */
|
|
26
|
+
@example(Examples.Phone.personalCollection)
|
|
27
|
+
@example(Examples.Phone.orgCollection)
|
|
28
|
+
model PhoneCollection {
|
|
29
|
+
/** The person's primary phone number. */
|
|
30
|
+
primary: Fields.Phone;
|
|
31
|
+
|
|
32
|
+
/** The person's fax number, if applicable. */
|
|
33
|
+
fax?: Fields.Phone;
|
|
34
|
+
|
|
35
|
+
/** Additional phone numbers not covered by the standard fields. */
|
|
36
|
+
otherPhones?: Record<Fields.Phone>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// #########################################################
|
|
40
|
+
// Examples
|
|
41
|
+
// #########################################################
|
|
42
|
+
|
|
43
|
+
namespace Examples.Phone {
|
|
44
|
+
const mobile = #{ countryCode: "+1", number: "444-456-1230", isMobile: true };
|
|
45
|
+
|
|
46
|
+
const home = #{ countryCode: "+1", number: "333-456-1230", isMobile: false };
|
|
47
|
+
|
|
48
|
+
const withExtension = #{
|
|
49
|
+
countryCode: "+1",
|
|
50
|
+
number: "555-123-4567",
|
|
51
|
+
extension: "123",
|
|
52
|
+
isMobile: false,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const personalCollection = #{ primary: mobile, otherPhones: #{ home: home } };
|
|
56
|
+
|
|
57
|
+
const orgCollection = #{
|
|
58
|
+
primary: mobile,
|
|
59
|
+
fax: withExtension,
|
|
60
|
+
otherPhones: #{
|
|
61
|
+
support: #{ countryCode: "+1", number: "333-456-1230", isMobile: false },
|
|
62
|
+
marketing: #{ countryCode: "+1", number: "444-456-1230", isMobile: true },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|