Skill: chloepark85/g2b-cli (version 0.1.0)

--- name: g2b-cli description: Korean public procurement (나라장터 g2b.go.kr) command-line wrapper for the 조달청 OpenAPI surface — three subcommands cover BidPublicInfoService (입찰공고 per 업무구분 thng/servc/cnstwk/frgcpt + PPS keyword search), CntrctInfoService (계약현황 per 업무구분), and PubDataOpnStdService (개방표준 unified bid/awarded/contract feeds). Use when monitoring government tenders, building bid-aggregator dashboards, scraping awarded-contract winners, performing supplier KYB on awardee 사업자번호, alerting subscribers to new RFPs by keyword, or backfilling historical procurement data into a warehouse. Pairs with nts-bizno-cli (resolve awardee 사업자등록번호), juso-address-cli (geocode 수요기관 addresses), kakao-local-cli (map pins), opendart-cli (cross-check awardee disclosures), kr-holiday-cli (calendar overlay for 공고 timing), and tistory-api-cli/velog-cli (publish daily tender digests). Free data.go.kr tier (1,000 req/day dev, expandable on prod approval). version: 0.1.0 license: MIT --- # g2b-cli Command-line wrapper for **조달청 나라장터 OpenAPI** — Korea's first-party public-procurement system. 나라장터 (g2b.go.kr) is the single national platform every Korean government agency, public university, and SOC project must use to post tenders worth roughly **₩200T** per year. The 조달청 (Public Procurement Service) exposes three OpenAPI services on data.go.kr that mirror the website's three core surfaces: 입찰공고, 계약현황, and the 개방표준 data feed. Three subcommands wrap the surface: | Command | Service | Purpose | |--------------------|----------------------------|----------------------------------------------------------------------------| | `scripts/bid.sh` | `BidPublicInfoService` | 입찰공고 (current bidding announcements) per 업무구분 + PPS keyword search. | | `scripts/contract.sh` | `CntrctInfoService` | 계약현황 (signed-contract listings) per 업무구분. | | `scripts/std.sh` | `PubDataOpnStdService` | 개방표준 unified feed: bid / awarded / contract on one canonical schema. | All output is **JSONL** — one row per record — so it pipes directly into `jq`, `csvkit`, `pandas`, or downstream skills. Pass `--meta` to any subcommand to get `{totalCount, pageNo, numOfRows}` instead of the items, which is the right shape for paging loops. ## When to use this skill - **Tender-monitoring dashboards** — `bid.sh --type servc --keyword "AI"` every hour to surface new AI-related RFPs nationwide. Pair with email/Slack alerts. - **Awarded-contract scrapers** — `std.sh --what awarded --from 202601010000 --to 202612310000` to backfill the year's 낙찰 records into a warehouse with a single, schema-stable feed. - **Supplier KYB on awardees** — pull `bizrno` from awarded records, fan out to **`nts-bizno-cli status`** to confirm the company is still 정상사업자 before you sign downstream paperwork. - **B2B sales prospecting** — `bid.sh --type thng --inquiry-div 5 --dminstt-cd <기관>` to monitor a target buyer institution's tender pipeline. - **Construction / SOC analysts** — `contract.sh --type cnstwk --from --to` for monthly 공사 contract volumes by region; pair with `kosis-cli` for population/GDP normalisation. - **Compliance audits** — repeated calls with `--meta` give totals; reconcile against a third-party data vendor's claims. - **News grounding for AI agents** — when an LLM answers "who won the Seoul subway camera tender?", cite real records from `std.sh --what awarded` instead of hallucinating. ## Do **not** use this skill for - **Bid submission** — the OpenAPI is read-only. Actual bid filing requires the 나라장터 GPKI/GBKI digital cert + browser session — that's still a Windows-only ActiveX/exe flow as of 2026. - **Pre-RFP procurement plans (사전공고)** — those live on a separate 조달정보표준 service not yet covered here. - **Sub-minute polling** — data.go.kr dev keys are 1,000 req/day. For live aggregation in production, request the prod tier on data.go.kr (justification + use-case form, ~3 business days). - **Outside-Korea procurement** — only 나라장터-listed Korean public tenders. For EU TED, US SAM.gov, or KONEPS/KOICA-funded foreign tenders, use those systems directly. - **Sub-public tenders (private B2B RFPs)** — Korea's 나라장터 does not host private-sector RFPs. Use third-party platforms (BidWeb, EProcurement) for that. ## Prerequisites 1. **Register at <https://www.data.go.kr/>** (free, no business required). 2. Apply for **all three** services (instant approval, dev tier; production tier needs a usage-case form): - "조달청_나라장터 입찰공고정보서비스" (BidPublicInfoService) - "조달청_나라장터 계약현황정보" (CntrctInfoService) - "조달청_나라장터 공공데이터개방표준서비스" (PubDataOpnStdService) 3. Copy the **Decoding** key (the raw form, *not* URL-encoded) and export: ```bash export G2B_SERVICE_KEY='your_decoded_key_here' ``` 4. Optional overrides (rarely needed; useful for staging): ```bash export G2B_BID_BASE='https://apis.data.go.kr/1230000/ad/BidPublicInfoService' export G2B_CNTRCT_BASE='https://apis.data.go.kr/1230000/ao/CntrctInfoService' export G2B_STD_BASE='https://apis.data.go.kr/1230000/ao/PubDataOpnStdService' ``` ## Common workflows ### A) Today's new 용역 (services) tenders ```bash scripts/bid.sh --type servc --rows 200 \ | jq -c '{bidNtceNo, bidNtceNm, ntceInsttNm, dminsttNm, presmptPrce, bidNtceDt, bidBeginDt}' ``` ### B) Keyword-driven RFP alert (AI-related 용역) ```bash scripts/bid.sh --keyword "인공지능" --rows 50 \ | jq -c 'select(.bidNtceDt >= "2026-04-01") | {bidNtceNo, bidNtceNm, presmptPrce, ntceInsttNm}' \ | tee today_ai_rfps.jsonl ``` ### C) Last-week awarded contracts → warehouse ```bash # Use the open-standard feed for warehouse-ready, schema-stable rows. scripts/std.sh --what awarded --from 202604220000 --to 202604290000 --rows 500 \ > awarded_2026wk17.jsonl ``` ### D) Awardee KYB chain (g2b → nts-bizno) ```bash # 1. Pull awarded list. scripts/std.sh --what awarded --rows 100 > awarded.jsonl # 2. For each awardee 사업자번호, confirm 정상사업자 status. jq -r '.bizrno // empty' awarded.jsonl | sort -u | while read brn; do nts-bizno-cli status --b-no "$brn" \ | jq --arg brn "$brn" '{bizrno: $brn, taxType: .tax_type, businessStatus: .b_stt}' done > awardee_kyb.jsonl ``` ### E) One specific 공고 detail (when you have the bidNtceNo) ```bash scripts/bid.sh --type thng --bid-no 20240412345-00 --rows 1 | jq . ``` ### F) Buyer-institution tender pipeline (수요기관 기준) ```bash # 6110000 = 서울특별시청 (example dminsttCd; look up via the 나라장터 institution dictionary). scripts/bid.sh --type cnstwk --inquiry-div 5 --dminstt-cd 6110000 --rows 200 \ | jq -c '{bidNtceNo, bidNtceNm, presmptPrce, opengDt}' ``` ### G) Pagination loop (full month dump) ```bash month_from=202604010000; month_to=202604300000 scripts/std.sh --what bid --from $month_from --to $month_to --meta # {"totalCount":4982,"pageNo":1,"numOfRows":100} for p in $(seq 1 50); do scripts/std.sh --what bid --from $month_from --to $month_to --rows 100 --page $p \ >> bid_2026_04.jsonl done ``` ## Data shapes (high-signal fields) `bid.sh` (BidPublicInfoService) — non-exhaustive list: | Field | Meaning | |------------------|------------------------------------------------| | `bidNtceNo` | 입찰공고번호 (primary key) | | `bidNtceOrd` | 차수 (revision; 00 = original) | | `bidNtceNm` | 입찰공고명 | | `ntceInsttNm` | 공고기관명 (the institution that *posted*) | | `dminsttNm` | 수요기관명 (the institution that *will use* it) | | `presmptPrce` | 추정가격 (원) | | `bidNtceDt` | 공고게시일자 | | `bidBeginDt` | 입찰개시일시 | | `opengDt` | 개찰일시 | | `bidMethdNm` | 입찰방식 | | `cntrctCnclsMthdNm` | 계약체결방법 | `contract.sh` (CntrctInfoService) — non-exhaustive list: | Field | Meaning | |------------------|------------------------------------------------| | `cntrctNo` | 계약번호 (primary key) | | `cntrctNm` | 계약명 | | `cntrctMthdNm` | 계약방식 | | `cntrctCnclsDate`| 계약체결일자 | | `cntrctPrce` | 계약금액 (원) | | `bizrno` | 낙찰자 사업자등록번호 | | `corpNm` | 낙찰자 상호 | `std.sh --what awarded` — non-exhaustive list: | Field | Meaning | |------------------|------------------------------------------------| | `bidNtceNo` | 입찰공고번호 | | `opengDate` | 개찰일자 | | `sucsfbidAmt` | 낙찰금액 (원) | | `sucsfbidRate` | 낙찰률 (%) | | `bizrno` | 낙찰자 사업자등록번호 | | `corpNm` | 낙찰자 상호 | Field names are stable inside each `--what` mode of `std.sh` but vary across the four 업무구분 in the non-std endpoints — always inspect a row before assuming a key exists. ## Pairs naturally with - **nts-bizno-cli** — resolve every `bizrno` in awarded records to a tax-type + business-status (KYB on the supplier side). - **juso-address-cli** — geocode 수요기관/공고기관 addresses to a 행정동 + WGS84. - **kakao-local-cli** — map-pin the resolved coordinates for a regional tender map. - **opendart-cli** — cross-check listed-company awardees against their public disclosures (ownership, financials, related-party). - **kr-holiday-cli** — calendar-overlay tenders to detect off-cycle posting (often a sign of urgency or favoritism). - **tistory-api-cli / velog-cli** — publish daily/weekly tender-digest posts (e.g., "AI 입찰 모음 — 2026-04-29"). ## Implementation notes - All endpoints accept both XML and JSON (`type=json`). This skill always asks for JSON; the wrapper will hard-fail with a clear message if the upstream returns XML (the 조달청 service silently falls back to XML on a serviceKey error). - Date params on g2b are **12-digit `YYYYMMDDHHMM`** — the wrapper validates this and provides a sensible default (last 7 days) when neither `--from`/`--to` nor `--bid-no` is given. - `inqryDiv` (조회구분) defaults match what the official portal example shows: `1` for date-window queries, `3` for `--bid-no`, `4` for `--instt-cd`, `5` for `--dminstt-cd`. Override with `--inquiry-div` if you need 등록 vs. 공고게시 vs. 개찰일자 selection. - The keyword search (`--keyword`) targets [REDACTED_SECRET] — only 용역 has a documented PPS keyword endpoint. For 물품/공사/외자, post-filter with `jq 'select(.bidNtceNm | test("키워드"))'` instead. - The wrapper validates the `response.header.resultCode == "00"` envelope and surfaces `resultMsg` on failure — common codes: `30` (서비스키 미등록), `22` (LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS — daily quota hit), `11` (NODATA_ERROR — empty result, *not* a failure shape; the wrapper still treats this as an error so you'll know upstream returned nothing). ## Examples See `examples/` for end-to-end recipes including: AI-keyword RFP digest, weekly 낙찰 ETL, awardee KYB pipeline, regional construction-contract map. ## Key takeaways - Three subcommands cover the entire 조달청 OpenAPI public surface. Pick `std.sh` first when you want one canonical schema; pick `bid.sh` / `contract.sh` when you need the per-업무구분 fields the open-standard feed strips. - This is the read side of 나라장터; bid submission is intentionally out of scope. - Pair with `nts-bizno-cli` for end-to-end awardee-vetting in a single shell pipeline — the missing piece between "the contract was awarded" and "the awardee is currently a 정상사업자".

path: README.md
content: # g2b-cli

CLI wrapper for **조달청 나라장터 OpenAPI** (Korea Public Procurement Service / g2b.go.kr) — read-only access to Korea's national tender system.

Three subcommands, JSONL output, free `data.go.kr` key.

| Command            | Service                    | Purpose                                                                  |
|--------------------|----------------------------|--------------------------------------------------------------------------|
| `scripts/bid.sh`      | `BidPublicInfoService`     | 입찰공고 (current bidding announcements) per 업무구분 + PPS keyword search. |
| `scripts/contract.sh` | `CntrctInfoService`        | 계약현황 (signed-contract listings) per 업무구분.                           |
| `scripts/std.sh`      | `PubDataOpnStdService`     | 개방표준 unified bid / awarded / contract feed.                             |

## Quick start

```bash
# 1. Get a key from data.go.kr (see SKILL.md for the three services to apply for).
export G2B_SERVICE_KEY='your_decoded_key_here'

# 2. Today's new 용역 tenders.
scripts/bid.sh --type servc --rows 200

# 3. AI-keyword RFP search.
scripts/bid.sh --keyword "인공지능" --rows 50

# 4. Last week's awarded contracts (warehouse-ready open-std feed).
scripts/std.sh --what awarded --from 202604220000 --to 202604290000 --rows 500
```

Run any subcommand with `--help` for full option lists.

## Output

JSONL — one record per line. Pipes directly into `jq`, `csvkit`, `pandas`, etc.

```bash
scripts/std.sh --what bid --rows 5 \
  | jq -c '{bidNtceNo, bidNtceNm, ntceInsttNm, presmptPrce}'
```

Pass `--meta` to any subcommand for `{totalCount, pageNo, numOfRows}` (use this to drive pagination loops).

## Pairs with

- **nts-bizno-cli** — KYB the awardees (resolve `bizrno` → 정상사업자 status).
- **juso-address-cli** — geocode 수요기관 addresses.
- **kakao-local-cli** — map-pin the coords.
- **opendart-cli** — cross-check listed-company awardees against disclosures.
- **tistory-api-cli / velog-cli** — publish daily tender digests.

## Auth

Free `data.go.kr` developer tier (1,000 req/day per service). Apply for the prod tier with a usage-case form when you need higher quotas.

## License

MIT.


path: examples/ai-rfp-digest.sh
content: #!/usr/bin/env bash
# ai-rfp-digest.sh — daily AI-related 용역 RFP digest.
# Pipe the output into Slack, email, or a tistory/velog post.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

today=$(date +%Y-%m-%d)
out=$(mktemp)

# Pull AI-keyworded 용역 RFPs from the last 24h.
"$here"/scripts/bid.sh --keyword "인공지능" --rows 100 \
  | jq -c "select(.bidNtceDt >= \"$today\")" \
  > "$out"

count=$(wc -l < "$out" | tr -d ' ')

cat <<EOF
# 🤖 AI 입찰 다이제스트 — $today

총 ${count}건의 인공지능 관련 신규 입찰공고가 게시되었습니다.

EOF

jq -r '"## \(.bidNtceNm)\n\n- 공고번호: \(.bidNtceNo)\n- 공고기관: \(.ntceInsttNm)\n- 수요기관: \(.dminsttNm)\n- 추정가격: ₩\(.presmptPrce)\n- 입찰개시: \(.bidBeginDt)\n- 개찰일시: \(.opengDt)\n"' "$out"

rm -f "$out"


path: examples/awardee-kyb-pipeline.sh
content: #!/usr/bin/env bash
# awardee-kyb-pipeline.sh — pull last-week awarded contracts and KYB each awardee.
#
# Output: JSONL — {bidNtceNo, sucsfbidAmt, bizrno, corpNm, taxType, businessStatus}
#
# Requires both g2b-cli and nts-bizno-cli installed in your skills dir.
# Set NTS_BIZNO_SERVICE_KEY to enable the second hop; without it, this script
# will only emit g2b columns and skip the KYB join.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
NTS_BIZNO="${NTS_BIZNO:-$(cd "$here/../nts-bizno-cli/scripts" && pwd)/status.sh}"

# Last 7 days, 500 rows. Bump --rows / paginate for higher-volume weeks.
read FROM TO < <("$here"/scripts/bid.sh --help >/dev/null 2>&1; \
  if date -v -7d +%Y%m%d >/dev/null 2>&1; then
    echo "$(date -v -7d +%Y%m%d)0000 $(date +%Y%m%d%H%M)";
  else
    echo "$(date -u -d '-7 days' +%Y%m%d)0000 $(date -u +%Y%m%d%H%M)";
  fi)

awarded=$(mktemp)
"$here"/scripts/std.sh --what awarded --from "$FROM" --to "$TO" --rows 500 > "$awarded"

if [[ ! -x "$NTS_BIZNO" ]] || [[ -z "${NTS_BIZNO_SERVICE_KEY:-}" ]]; then
  echo "warning: nts-bizno-cli or NTS_BIZNO_SERVICE_KEY not configured — emitting g2b-only rows." >&2
  jq -c '{bidNtceNo, sucsfbidAmt, bizrno, corpNm}' "$awarded"
  rm -f "$awarded"
  exit 0
fi

# Group by bizrno to avoid duplicate KYB calls.
declare -A status_cache
while IFS= read -r row; do
  brn=$(jq -r '.bizrno // empty' <<<"$row")
  [[ -z "$brn" ]] && { jq -c '{bidNtceNo, sucsfbidAmt, bizrno, corpNm, taxType: null, businessStatus: null}' <<<"$row"; continue; }

  if [[ -z "${status_cache[$brn]:-}" ]]; then
    s=$("$NTS_BIZNO" --b-no "$brn" 2>/dev/null \
        | jq -c '{taxType: .tax_type, businessStatus: .b_stt}' || echo '{"taxType":null,"businessStatus":null}')
    status_cache[$brn]="$s"
  fi

  jq -c --argjson s "${status_cache[$brn]}" '{bidNtceNo, sucsfbidAmt, bizrno, corpNm} + $s' <<<"$row"
done < "$awarded"

rm -f "$awarded"


path: scripts/_common.sh
content: #!/usr/bin/env bash
# _common.sh — shared helpers for g2b-cli.
# Source this from each subcommand: `. "$here/_common.sh"`
#
# Wraps three 조달청 (Public Procurement Service) services on data.go.kr —
# the public APIs behind 나라장터 (g2b.go.kr):
#   - BidPublicInfoService:    current bid announcements per 업무구분.
#   - CntrctInfoService:       contract / 계약현황 per 업무구분.
#   - PubDataOpnStdService:    개방표준 (unified) bid / awarded / contract feeds.

set -euo pipefail

G2B_BID_BASE="${G2B_BID_BASE:-https://apis.data.go.kr/1230000/ad/BidPublicInfoService}"
G2B_CNTRCT_BASE="${G2B_CNTRCT_BASE:-https://apis.data.go.kr/1230000/ao/CntrctInfoService}"
G2B_STD_BASE="${G2B_STD_BASE:-https://apis.data.go.kr/1230000/ao/PubDataOpnStdService}"

require_bin() {
  for b in "$@"; do
    command -v "$b" >/dev/null 2>&1 || { echo "error: $b is required but not installed." >&2; exit 127; }
  done
}

require_key() {
  if [[ -z "${G2B_SERVICE_KEY:-}" ]]; then
    cat >&2 <<'EOF'
error: G2B_SERVICE_KEY must be set.
Register at https://www.data.go.kr/ and request approval (instant for dev tier) for:
  "조달청_나라장터 입찰공고정보서비스"   (BidPublicInfoService)
  "조달청_나라장터 계약현황정보"           (CntrctInfoService)
  "조달청_나라장터 공공데이터개방표준서비스"  (PubDataOpnStdService)
Then export the *Decoding* key (raw, not URL-encoded) as G2B_SERVICE_KEY.
EOF
    exit 78
  fi
}

# valid_bsns <thng|servc|cnstwk|frgcpt>  — 업무구분 (goods / service / construction / foreign).
valid_bsns() {
  case "$1" in
    thng|servc|cnstwk|frgcpt) ;;
    *) echo "error: --type '$1' must be one of: thng (물품) servc (용역) cnstwk (공사) frgcpt (외자)." >&2; exit 64;;
  esac
}

# valid_inqry_div <1..5>  — 조회구분.
#   1=등록일시, 2=공고게시일자, 3=공고번호, 4=공고기관코드, 5=수요기관코드
valid_inqry_div() {
  case "$1" in
    1|2|3|4|5) ;;
    *) echo "error: --inquiry-div '$1' must be 1..5 (1=등록 2=공고게시 3=공고번호 4=공고기관 5=수요기관)." >&2; exit 64;;
  esac
}

# valid_dt12 <YYYYMMDDHHMM>  — 12-digit timestamp required by data.go.kr g2b.
valid_dt12() {
  if ! [[ "$1" =~ ^[0-9]{12}$ ]]; then
    echo "error: timestamp '$1' must be YYYYMMDDHHMM (12 digits, e.g. 202604300000)." >&2
    exit 64
  fi
}

# default_window  — emits "FROM TO" for the last N days (default 7) as YYYYMMDDHHMM.
default_window() {
  local days="${1:-7}"
  local now from
  now=$(date -u +%Y%m%d%H%M)
  if date -v -1d +%Y%m%d >/dev/null 2>&1; then
    from=$(date -v -"${days}"d +%Y%m%d)0000
  else
    from=$(date -u -d "-${days} days" +%Y%m%d)0000
  fi
  printf '%s %s\n' "$from" "$now"
}

# g2b_get <base> <path> <key=val> ...
# - Adds serviceKey, type=json automatically.
# - URL-encodes values via jq -r @uri.
# - Returns body on stdout. Non-2xx, XML envelopes, or non-OK resultCode → stderr + exit 22.
g2b_get() {
  local base="$1" path="$2"; shift 2
  require_key

  local enc_key
  enc_key=$(printf '%s' "$G2B_SERVICE_KEY" | jq -sRr '@uri')
  local qs="serviceKey=${enc_key}&type=json"

  local kv k v enc_v
  for kv in "$@"; do
    [[ -z "$kv" ]] && continue
    k="${kv%%=*}"
    v="${kv#*=}"
    [[ -z "$v" ]] && continue
    enc_v=$(printf '%s' "$v" | jq -sRr '@uri')
    qs="${qs}&${k}=${enc_v}"
  done

  local out http
  out=$(mktemp)
  http=$(curl -sS -G -o "$out" -w '%{http_code}' "${base}/${path}?${qs}" || true)

  if [[ "$http" != 2* ]]; then
    echo "error: g2b returned HTTP $http for /${path}" >&2
    sed -e 's/^/  /' "$out" >&2
    rm -f "$out"
    exit 22
  fi

  # data.go.kr sometimes returns XML (with a SERVICE_KEY error envelope) even when type=json.
  if head -c 1 "$out" | grep -q '<'; then
    echo "error: g2b returned XML (likely a service-key, quota, or path error) for /${path}" >&2
    sed -e 's/^/  /' "$out" >&2
    rm -f "$out"
    exit 22
  fi

  local rc rmsg
  rc=$(jq -r '.response.header.resultCode // "?"' "$out")
  rmsg=$(jq -r '.response.header.resultMsg  // ""'  "$out")
  if [[ "$rc" != "00" ]]; then
    echo "error: g2b resultCode=$rc resultMsg=$rmsg for /${path}" >&2
    rm -f "$out"
    exit 22
  fi

  cat "$out"
  rm -f "$out"
}

# emit_items <body-json>
# Extracts response.body.items as JSONL. Handles array, single-object, and empty shapes.
emit_items() {
  jq -c '
    .response.body.items as $it
    | if   $it == null then empty
      elif ($it | type) == "array"  then $it[]
      elif ($it | type) == "object" and ($it.item != null)
        then if ($it.item | type) == "array" then $it.item[] else $it.item end
      else $it
      end
  '
}

# emit_meta <body-json>
# Prints response.body summary: totalCount, pageNo, numOfRows on a single JSON line.
emit_meta() {
  jq -c '{totalCount: (.response.body.totalCount // null), pageNo: (.response.body.pageNo // null), numOfRows: (.response.body.numOfRows // null)}'
}


path: scripts/bid.sh
content: #!/usr/bin/env bash
# bid.sh — BidPublicInfoService: 나라장터 입찰공고 (current bidding announcements).
#
# Wraps four 업무구분 endpoints under one flag (--type):
#   thng    → getBidPblancListInfoThng     (물품)
#   servc   → getBidPblancListInfoServc    (용역)
#   cnstwk  → getBidPblancListInfoCnstwk   (공사)
#   frgcpt  → getBidPblancListInfoFrgcpt   (외자)
# Plus a keyword-search variant under --keyword (uses getBidPblancListInfoServcPPSSrch
# for 용역 — the only 업무구분 with a documented PPS keyword endpoint).
#
# Usage:
#   bid.sh --type thng                          # 물품 last 7 days
#   bid.sh --type servc --from 202604010000 --to 202604300000
#   bid.sh --type cnstwk --inquiry-div 5 --dminstt-cd 6110000
#   bid.sh --type thng --bid-no 20240412345-00
#   bid.sh --keyword 인공지능 --rows 50           # PPS 검색 (servc only)
#   bid.sh --type thng --meta                   # totalCount / pageNo / numOfRows only
#
# Output: JSONL — one row per 입찰공고. Use --meta for the response summary instead.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$here/_common.sh"

bsns="thng" inquiry_div="" from="" to="" rows="100" page="1" bid_no="" instt_cd="" dminstt_cd="" keyword="" meta=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --type)          bsns="$2";        shift 2;;
    --inquiry-div)   inquiry_div="$2"; shift 2;;
    --from)          from="$2";        shift 2;;
    --to)            to="$2";          shift 2;;
    --rows)          rows="$2";        shift 2;;
    --page)          page="$2";        shift 2;;
    --bid-no)        bid_no="$2";      shift 2;;
    --instt-cd)      instt_cd="$2";    shift 2;;
    --dminstt-cd)    dminstt_cd="$2";  shift 2;;
    --keyword)       keyword="$2";     shift 2;;
    --meta)          meta=1;           shift;;
    -h|--help)
      sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'
      exit 0;;
    *) echo "error: unknown flag '$1'" >&2; exit 64;;
  esac
done

require_bin curl jq

# Resolve the operation name + applicable params.
if [[ -n "$keyword" ]]; then
  op="getBidPblancListInfoServcPPSSrch"  # PPS 키워드 검색 — servc only
else
  valid_bsns "$bsns"
  case "$bsns" in
    thng)   op="getBidPblancListInfoThng";;
    servc)  op="getBidPblancListInfoServc";;
    cnstwk) op="getBidPblancListInfoCnstwk";;
    frgcpt) op="getBidPblancListInfoFrgcpt";;
  esac
fi

# Default 7-day window unless explicit --from/--to or --bid-no was given.
if [[ -z "$bid_no" && ( -z "$from" || -z "$to" ) ]]; then
  read from to < <(default_window 7)
fi
[[ -n "$from" ]] && valid_dt12 "$from"
[[ -n "$to"   ]] && valid_dt12 "$to"
[[ -n "$inquiry_div" ]] && valid_inqry_div "$inquiry_div"

# 공고번호 단건 조회는 inqryDiv=3 + bidNtceNo만 보내면 충분 (날짜창 미필요).
declare -a params=( "numOfRows=$rows" "pageNo=$page" )

if [[ -n "$bid_no" ]]; then
  params+=( "inqryDiv=3" "bidNtceNo=$bid_no" )
elif [[ -n "$dminstt_cd" ]]; then
  params+=( "inqryDiv=${inquiry_div:-5}" "dminsttCd=$dminstt_cd" "inqryBgnDt=$from" "inqryEndDt=$to" )
elif [[ -n "$instt_cd" ]]; then
  params+=( "inqryDiv=${inquiry_div:-4}" "ntceInsttCd=$instt_cd" "inqryBgnDt=$from" "inqryEndDt=$to" )
else
  # Default to inqryDiv=1 (등록일시 기준) when only the date window is given.
  params+=( "inqryDiv=${inquiry_div:-1}" "inqryBgnDt=$from" "inqryEndDt=$to" )
fi

[[ -n "$keyword" ]] && params+=( "bidNtceNm=$keyword" )

resp=$(g2b_get "$G2B_BID_BASE" "$op" "${params[@]}")

if (( meta )); then
  echo "$resp" | emit_meta
else
  echo "$resp" | emit_items
fi


path: scripts/contract.sh
content: #!/usr/bin/env bash
# contract.sh — CntrctInfoService: 나라장터 계약현황 (signed contracts per 업무구분).
#
# Wraps four 업무구분 endpoints under one flag (--type):
#   thng    → getCntrctInfoListThng     (물품)
#   servc   → getCntrctInfoListServc    (용역)
#   cnstwk  → getCntrctInfoListCnstwk   (공사)
#   frgcpt  → getCntrctInfoListFrgcpt   (외자)
#
# Usage:
#   contract.sh --type thng                          # 물품 last 7 days
#   contract.sh --type servc --from 202604010000 --to 202604300000
#   contract.sh --type cnstwk --inquiry-div 4 --instt-cd 6110000
#   contract.sh --type thng --cntrct-no 2026000123 --meta
#
# Output: JSONL — one row per 계약. Use --meta for the response summary instead.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$here/_common.sh"

bsns="thng" inquiry_div="" from="" to="" rows="100" page="1" cntrct_no="" instt_cd="" dminstt_cd="" meta=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --type)          bsns="$2";        shift 2;;
    --inquiry-div)   inquiry_div="$2"; shift 2;;
    --from)          from="$2";        shift 2;;
    --to)            to="$2";          shift 2;;
    --rows)          rows="$2";        shift 2;;
    --page)          page="$2";        shift 2;;
    --cntrct-no)     cntrct_no="$2";   shift 2;;
    --instt-cd)      instt_cd="$2";    shift 2;;
    --dminstt-cd)    dminstt_cd="$2";  shift 2;;
    --meta)          meta=1;           shift;;
    -h|--help)
      sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
      exit 0;;
    *) echo "error: unknown flag '$1'" >&2; exit 64;;
  esac
done

require_bin curl jq
valid_bsns "$bsns"

case "$bsns" in
  thng)   op="getCntrctInfoListThng";;
  servc)  op="getCntrctInfoListServc";;
  cnstwk) op="getCntrctInfoListCnstwk";;
  frgcpt) op="getCntrctInfoListFrgcpt";;
esac

if [[ -z "$cntrct_no" && ( -z "$from" || -z "$to" ) ]]; then
  read from to < <(default_window 7)
fi
[[ -n "$from" ]] && valid_dt12 "$from"
[[ -n "$to"   ]] && valid_dt12 "$to"
[[ -n "$inquiry_div" ]] && valid_inqry_div "$inquiry_div"

declare -a params=( "numOfRows=$rows" "pageNo=$page" )

if [[ -n "$cntrct_no" ]]; then
  # 계약번호 단건 조회 (inqryDiv=2)
  params+=( "inqryDiv=2" "cntrctNo=$cntrct_no" )
elif [[ -n "$dminstt_cd" ]]; then
  params+=( "inqryDiv=${inquiry_div:-3}" "dminsttCd=$dminstt_cd" "inqryBgnDt=$from" "inqryEndDt=$to" )
elif [[ -n "$instt_cd" ]]; then
  params+=( "inqryDiv=${inquiry_div:-4}" "ntceInsttCd=$instt_cd" "inqryBgnDt=$from" "inqryEndDt=$to" )
else
  params+=( "inqryDiv=${inquiry_div:-1}" "inqryBgnDt=$from" "inqryEndDt=$to" )
fi

resp=$(g2b_get "$G2B_CNTRCT_BASE" "$op" "${params[@]}")

if (( meta )); then
  echo "$resp" | emit_meta
else
  echo "$resp" | emit_items
fi


path: scripts/std.sh
content: #!/usr/bin/env bash
# std.sh — PubDataOpnStdService: 공공데이터개방표준 (unified, schema-stable feeds).
#
# One subcommand for all three open-standard data sets:
#   bid       → getDataSetOpnStdBidPblancInfo  (입찰공고 표준)
#   awarded   → getDataSetOpnStdScsbidInfo     (낙찰 표준)
#   contract  → getDataSetOpnStdCntrctInfo     (계약 표준)
#
# Why "std"?
#   The non-std endpoints (BidPublicInfoService, CntrctInfoService) have separate
#   shapes per 업무구분 (thng/servc/cnstwk/frgcpt). The 개방표준 service collapses
#   all four 업무구분 into a single canonical schema — easier for warehouses + dashboards.
#
# Usage:
#   std.sh --what bid                         # last 7 days, 100 rows
#   std.sh --what awarded --from 202604010000 --to 202604300000 --rows 500
#   std.sh --what contract --bid-no 20240412345-00 --meta
#
# Output: JSONL — one row per record. Use --meta for the response summary instead.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$here/_common.sh"

what="" from="" to="" rows="100" page="1" bid_no="" instt_cd="" dminstt_cd="" meta=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --what)          what="$2";        shift 2;;
    --from)          from="$2";        shift 2;;
    --to)            to="$2";          shift 2;;
    --rows)          rows="$2";        shift 2;;
    --page)          page="$2";        shift 2;;
    --bid-no)        bid_no="$2";      shift 2;;
    --instt-cd)      instt_cd="$2";    shift 2;;
    --dminstt-cd)    dminstt_cd="$2";  shift 2;;
    --meta)          meta=1;           shift;;
    -h|--help)
      sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'
      exit 0;;
    *) echo "error: unknown flag '$1'" >&2; exit 64;;
  esac
done

require_bin curl jq

case "$what" in
  bid)      op="getDataSetOpnStdBidPblancInfo";;
  awarded)  op="getDataSetOpnStdScsbidInfo";;
  contract) op="getDataSetOpnStdCntrctInfo";;
  *) echo "error: --what must be one of: bid awarded contract." >&2; exit 64;;
esac

if [[ -z "$bid_no" && ( -z "$from" || -z "$to" ) ]]; then
  read from to < <(default_window 7)
fi
[[ -n "$from" ]] && valid_dt12 "$from"
[[ -n "$to"   ]] && valid_dt12 "$to"

declare -a params=( "numOfRows=$rows" "pageNo=$page" )

# The std service uses different begin/end-date param names per dataset.
# All accept bidNtceBgnDt/bidNtceEndDt as the canonical filter window.
if [[ -n "$bid_no" ]]; then
  params+=( "bidNtceNo=$bid_no" )
else
  params+=( "bidNtceBgnDt=$from" "bidNtceEndDt=$to" )
fi
[[ -n "$instt_cd"   ]] && params+=( "ntceInsttCd=$instt_cd" )
[[ -n "$dminstt_cd" ]] && params+=( "dminsttCd=$dminstt_cd" )

resp=$(g2b_get "$G2B_STD_BASE" "$op" "${params[@]}")

if (( meta )); then
  echo "$resp" | emit_meta
else
  echo "$resp" | emit_items
fi


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)