Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/ontology/adapters/calc.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/ontology/adapters/calc.go |
| Original SHA-256 | 0dad59f629d6a14304f669bbb6212a8281d32f475f8b1e5eaae3e38add4fbe95 |
| Git blob SHA | 80386392fc7e7cbff74d65499e3d12be1c24477e |
| Reading-copy SHA-256 | 807c158300e7893b07abef6d4d48d34390a8b87520f9b5850afa9cec5cdbe48a |
Every recorded occurrence
These 20 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
| Janady13/missionmatch / internal/modules/ontology/adapters/calc.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓186 lines · 5.7 KB
Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.
package adapters
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/missionworkforce/missionmatch/internal/modules/ontology"
)
// defaultCALCSeeds is the vocabulary sweep list used when the caller does not supply seeds.
// Each term is sent as the q parameter to the CALC ceiling rates API to enumerate the distinct
// labor category vocabulary. The terms are broad enough to cover the common GovCon labor
// taxonomy without requiring a dedicated catalog endpoint (CALC has none).
var defaultCALCSeeds = []string{
"engineer",
"analyst",
"manager",
"architect",
"specialist",
"developer",
"scientist",
"administrator",
"consultant",
"technician",
}
// CALCSource ingests the GSA CALC (Contract-Awarded Labor Category) ceiling rates vocabulary by
// sweeping a seed list of common terms against the CALC v3 API, deduplicating the returned labor
// category strings, and yielding one KindLaborCategory VocabRecord per distinct category.
//
// The CALC API is Elasticsearch backed and has no dedicated catalog endpoint, so a seed sweep is
// the correct enumeration strategy. The real response shape (confirmed from
// https://api.gsa.gov/acquisition/calc/v3/api/ceilingrates/?q=engineer) is:
//
// hits.hits[]._source.labor_category
type CALCSource struct {
baseURL string
apiKey string
seeds []string
client *http.Client
}
// NewCALCSource builds the adapter. baseURL is the CALC ceiling rates endpoint (injected so tests
// can point at an httptest.Server). apiKey is the optional DATA_GOV_API_KEY that lifts the gateway
// rate limit; pass an empty string when no key is available. seeds overrides the default seed list;
// pass nil to use the built-in defaults. client is the HTTP client; pass nil for a 60s default.
func NewCALCSource(baseURL, apiKey string, seeds []string, client *http.Client) *CALCSource {
if client == nil {
client = &http.Client{Timeout: 60 * time.Second}
}
if len(seeds) == 0 {
seeds = defaultCALCSeeds
}
return &CALCSource{
baseURL: strings.TrimRight(baseURL, "/") + "/",
apiKey: [credential removed],
seeds: seeds,
client: client,
}
}
// Name is the provenance label stamped on every record this source yields.
func (s *CALCSource) Name() string { return "GSA CALC" }
// Fetch sweeps the seed list against the CALC API, deduplicates labor categories across all
// seed responses, and yields one VocabRecord per distinct labor category. A 429 from the
// provider returns ontology.ErrThrottled so the ingester can stop gracefully.
func (s *CALCSource) Fetch(ctx context.Context, yield func(ontology.VocabRecord) error) error {
seen := make(map[string]bool)
for _, seed := range s.seeds {
raw, status, err := s.get(ctx, seed)
if err != nil {
return fmt.Errorf("calc: fetch seed %q: %w", seed, err)
}
if status == http.StatusTooManyRequests {
return ontology.ErrThrottled
}
if status != http.StatusOK {
return fmt.Errorf("calc: seed %q: provider returned status %d", seed, status)
}
categories, err := parseCALC(raw)
if err != nil {
return fmt.Errorf("calc: parse seed %q: %w", seed, err)
}
for _, lc := range categories {
key := strings.ToLower(lc)
if seen[key] {
continue
}
seen[key] = true
rawJSON, _ := json.Marshal(map[string]string{
"labor_category": lc,
"source": "GSA CALC ceiling rates API",
})
if err := yield(ontology.VocabRecord{
SourceName: "GSA CALC",
SourceRecordID: key,
Kind: ontology.KindLaborCategory,
Term: lc,
Category: "labor_category",
Raw: rawJSON,
}); err != nil {
return err
}
}
}
return nil
}
// get performs one GET request against the CALC ceiling rates endpoint for the given search term
// and returns the raw body bytes plus the HTTP status code.
func (s *CALCSource) get(ctx context.Context, q string) ([]byte, int, error) {
u, err := url.Parse(s.baseURL)
if err != nil {
return nil, 0, fmt.Errorf("calc: parse baseURL: %w", err)
}
params := url.Values{"q": {q}}
if s.apiKey != "" {
params.Set("api_key", s.apiKey)
}
u.RawQuery = params.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, 0, fmt.Errorf("calc: new request: %w", err)
}
resp, err := s.client.Do(req)
if err != nil {
return nil, 0, fmt.Errorf("calc: do request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, resp.StatusCode, fmt.Errorf("calc: read body: %w", err)
}
return body, resp.StatusCode, nil
}
// calcResponse mirrors the Elasticsearch backed shape of the GSA CALC v3 ceiling rates API.
// Confirmed from https://api.gsa.gov/acquisition/calc/v3/api/ceilingrates/?q=engineer:
// labor category lives at hits.hits[]._source.labor_category.
type calcResponse struct {
Hits struct {
Hits []struct {
Source struct {
LaborCategory string `json:"labor_category"`
} `json:"_source"`
} `json:"hits"`
} `json:"hits"`
}
// parseCALC extracts the distinct labor category strings from one CALC ceiling rates response
// page. It is kept pure (no I/O) so it is directly unit testable. Deduplication within a single
// page is applied here; cross-seed deduplication is the caller's responsibility.
func parseCALC(data []byte) ([]string, error) {
var doc calcResponse
if err := json.Unmarshal(data, &doc); err != nil {
return nil, fmt.Errorf("calc: decode JSON: %w", err)
}
seen := make(map[string]bool)
var out []string
for _, hit := range doc.Hits.Hits {
lc := strings.TrimSpace(hit.Source.LaborCategory)
if lc == "" {
continue
}
key := strings.ToLower(lc)
if !seen[key] {
seen[key] = true
out = append(out, lc)
}
}
return out, nil
}