Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Routine `stats.json` refreshes take a PATCH version bump but are grouped rather than
listed individually.

## [0.29.1] - 2026-09-25

### Fixed

- The closing note under *Watchlist monitoring* and *Integrity checks* sat
flush against the last card; it now has the section's spacing.

## [0.29.0] - 2026-09-25

### Added

- **Fraud page: *Watchlist monitoring*** (EN, KO) — identifiers the buyer
hands over, checked nightly against the whole collection; baseline versus
new; a count-only mail with the detail behind the buyer's own sign-in; a
monthly one-page summary; and what "not seen" does not mean. Turkish
carries no section: that market is not sold.
- **Procurement page: *Integrity checks*** (EN, KO) — related bidders who
meet in tenders, closed companies still bidding (국세청 status), priced
bids and awards only (anonymised entrants left out), 법인 only. Methods
only; no firm is named.
- Home cards for fraud and procurement mention the two.

### Note

- The Korean copy was written by the agent and needs a native read before
it is merged.

## [0.28.2] - 2026-09-22

### Changed
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.28.9
0.29.1
17 changes: 17 additions & 0 deletions src/components/Fraud.astro
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ const n = copy.numbers;
</div>
</section>

{copy.watch && (
<section class="section" id="watchlist">
<div class="eyebrow">{copy.watch.heading}</div>
<p class="muted">{copy.watch.lead}</p>
<div class="grid">
{copy.watch.blocks.map((b) => (
<article class="card line-fraud">
<div class="card__line"></div>
<h3>{b.title}</h3>
<p>{b.body}</p>
</article>
))}
</div>
<p class="muted" style="margin-top: var(--s-4)">{copy.watch.note}</p>
</section>
)}

<section class="section" id="coverage">
<div class="eyebrow">{copy.coverage.heading}</div>
<p class="muted">{copy.coverage.lead}</p>
Expand Down
17 changes: 17 additions & 0 deletions src/components/Procurement.astro
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ const { copy, locale } = Astro.props;
</div>
</section>

{copy.integrity && (
<section class="section" id="integrity">
<div class="eyebrow">{copy.integrity.heading}</div>
<p class="muted">{copy.integrity.lead}</p>
<div class="grid">
{copy.integrity.blocks.map((b) => (
<article class="card line-procurement">
<div class="card__line"></div>
<h3>{b.title}</h3>
<p>{b.body}</p>
</article>
))}
</div>
<p class="muted" style="margin-top: var(--s-4)">{copy.integrity.note}</p>
</section>
)}

<section class="section" id="how">
<div class="eyebrow">{copy.how.heading}</div>
<p>{copy.how.body}</p>
Expand Down
27 changes: 26 additions & 1 deletion src/i18n/fraud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export interface FraudCopy {
description: string;
hero: { eyebrow: string; title: string; lead: string; live: string; primary: string };
what: { heading: string; lead: string; blocks: Block[] };
// Optional: the watchlist subscription (2026-09-25). Rendered only where a
// locale carries it; Turkish does not, because the TR market is not sold.
watch?: { heading: string; lead: string; blocks: Block[]; note: string };
coverage: { heading: string; lead: string; blocks: Block[] };
numbers: { heading: string; lead: string; entities: string; bank: string; phone: string; crypto: string; clusters: string };
delivery: { heading: string; body: string };
Expand Down Expand Up @@ -48,6 +51,17 @@ export const fraud: Record<Locale, FraudCopy> = {
{ title: 'Deliver', body: 'Machine-readable watchlists (CSV, MISP IOC), bank-specific alert feeds, STIX bundles, an authenticated REST API and webhooks for SIEM/SOAR, and evidence-grade reports with confidence scoring, audit trails and legal reference mappings for every finding.' },
],
},
watch: {
heading: 'Watchlist monitoring',
lead: 'Hand over the identifiers you care about — accounts, phone numbers, wallets, channels. Every night they are checked against everything the platform collects, and you are told when something new appears.',
blocks: [
{ title: 'Your list, checked nightly', body: 'Paste a list once. Each identifier is read the way it was written — a number that could be a phone or a bank account is watched as both — and checked against the whole collection every night.' },
{ title: 'Baseline, then news', body: 'What the platform already knew when you added an identifier is shown as the baseline. Only what arrives afterwards counts as new, so an alert means something changed.' },
{ title: 'Counts by mail, detail behind your sign-in', body: 'The nightly mail says how many new records appeared — never which identifier. The detail, with the record behind every link, is on the console behind your own sign-in.' },
{ title: 'One page a month', body: 'A monthly one-page summary per list: what was watched, what the platform knows, what changed and of what kind — ready to print or file.' },
],
note: '"Not seen" means the platform has not observed an identifier in what it collects. It does not mean the identifier is clean.',
},
coverage: {
heading: 'Coverage',
lead: 'Active collection across the markets below; Turkish-language ecosystems are monitored and can be scoped into a subscription.',
Expand All @@ -71,7 +85,7 @@ export const fraud: Record<Locale, FraudCopy> = {
},
delivery: {
heading: 'How it arrives',
body: 'Nightly. A subscription is scoped to your market and your watchlist and arrives by email or webhook: new entities, escalations and networks that have gone quiet — scored, deduplicated, evidence attached. Curated feeds, bank risk briefs and the managed intelligence package are scoped per buyer. Pricing on request.',
body: 'Nightly. A subscription is scoped to your market and your watchlist and arrives by email or webhook: new entities, escalations and networks that have gone quiet — scored, deduplicated, evidence attached. A watchlist subscription adds your own identifiers to the nightly check. Curated feeds, bank risk briefs and the managed intelligence package are scoped per buyer. Pricing on request.',
},
contact: {
heading: 'Get in touch',
Expand Down Expand Up @@ -103,6 +117,17 @@ export const fraud: Record<Locale, FraudCopy> = {
{ title: '전달', body: '기계 판독 가능한 감시 목록(CSV, MISP IOC), 은행별 알림 피드, STIX 번들, 인증된 REST API와 SIEM/SOAR용 웹훅, 그리고 모든 발견 사항에 신뢰도 점수·감사 추적·법적 참조를 담은 증거 수준 보고서.' },
],
},
watch: {
heading: '워치리스트 모니터링',
lead: '관심 있는 계좌, 전화번호, 지갑 주소, 채널을 알려주시면 매일 밤 플랫폼이 수집한 전체 데이터와 대조하고, 새로운 기록이 생기면 알려드립니다.',
blocks: [
{ title: '매일 밤 확인하는 목록', body: '목록은 한 번만 등록하면 됩니다. 각 식별자는 입력된 형태 그대로 읽으며, 전화번호와 계좌번호 둘 다로 해석될 수 있는 번호는 두 가지 모두로 감시하고, 매일 밤 전체 수집 데이터와 대조합니다.' },
{ title: '기준선, 그리고 새 기록', body: '등록 시점에 플랫폼이 이미 알고 있던 내용은 기준선으로 표시하고, 그 이후에 들어온 것만 새 기록으로 셉니다. 알림이 왔다면 실제로 무언가 바뀌었다는 뜻입니다.' },
{ title: '메일은 건수만, 상세는 로그인 후', body: '매일 밤 메일에는 새 기록의 건수만 담기며, 어떤 식별자인지는 적지 않습니다. 각 연결의 근거 기록을 포함한 상세 내용은 본인 로그인 후 콘솔에서 확인합니다.' },
{ title: '월 1페이지 요약', body: '목록별 월간 1페이지 요약: 감시 대상, 플랫폼이 파악한 대상, 변화의 건수와 종류를 정리해 출력하거나 보관할 수 있습니다.' },
],
note: '"미확인"은 플랫폼이 수집 범위에서 해당 식별자를 관측하지 못했다는 뜻이며, 문제가 없다는 뜻은 아닙니다.',
},
coverage: {
heading: '커버리지',
lead: '아래 시장에서 적극적으로 수집합니다. 터키어 생태계는 모니터링 중이며 구독 범위에 포함할 수 있습니다.',
Expand Down
8 changes: 4 additions & 4 deletions src/i18n/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ export const home: Record<HomeLocale, HomeCopy> = {
fraud: {
badge: 'Fraud',
title: 'Fraud Intelligence',
body: 'Telegram and OSINT monitoring of criminal and grey-market ecosystems — financial crime, illegal betting, drug trafficking and laundering — delivered as feeds, alerts and evidence-grade reports.',
body: 'Telegram and OSINT monitoring of criminal and grey-market ecosystems — financial crime, illegal betting, drug trafficking and laundering — delivered as feeds, alerts, evidence-grade reports and a nightly watchlist of your own identifiers.',
action: 'Request a sample report →',
href: '/fraud',
},
procurement: {
badge: 'Procurement',
title: 'Procurement Intelligence',
body: 'Every Korean public tender, bidder and winning price — against the published estimate. Collected nightly, nationwide, not sampled.',
body: 'Every Korean public tender, bidder and winning price — against the published estimate. Collected nightly, nationwide, not sampled — and read for integrity: related bidders, and closed companies still bidding.',
action: 'See what we collect →',
href: '/procurement',
},
Expand Down Expand Up @@ -132,14 +132,14 @@ export const home: Record<HomeLocale, HomeCopy> = {
fraud: {
badge: '사기',
title: '사기 인텔리전스',
body: '텔레그램과 OSINT로 범죄·회색시장 생태계를 모니터링합니다 — 금융범죄, 불법도박, 마약, 자금세탁 — 피드, 알림, 증거 수준 보고서로 전달합니다.',
body: '텔레그램과 OSINT로 범죄·회색시장 생태계를 모니터링합니다 — 금융범죄, 불법도박, 마약, 자금세탁 — 피드, 알림, 증거 수준 보고서, 그리고 고객이 지정한 식별자를 매일 밤 확인하는 워치리스트로 전달합니다.',
action: '샘플 보고서 요청 →',
href: '/ko/fraud',
},
procurement: {
badge: '공공조달',
title: '공공조달 인텔리전스',
body: '한국 공공입찰의 모든 공고, 입찰자, 낙찰가를 기초금액과 비교합니다. 매일 밤 전국 단위로 수집하며, 표본이 아닙니다.',
body: '한국 공공입찰의 모든 공고, 입찰자, 낙찰가를 기초금액과 비교합니다. 매일 밤 전국 단위로 수집하며, 표본이 아닙니다. 관계 업체의 반복 동반 투찰, 폐업 후 입찰 같은 청렴성 점검도 함께 실행합니다.',
action: '수집 항목 보기 →',
href: '/ko/procurement',
},
Expand Down
25 changes: 25 additions & 0 deletions src/i18n/procurement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export interface ProcurementCopy {
problem: { heading: string; body: string };
collect: { heading: string; lead: string; liveBadge: string; blocks: Block[] };
coverage: { heading: string; blocks: Block[] };
// Optional: the integrity checks (2026-09-25) -- methods only, no firm is
// ever named on the site.
integrity?: { heading: string; lead: string; blocks: Block[]; note: string };
how: { heading: string; body: string; platform: string };
figures: { heading: string; lead: string; more: string; moreLink: string; rateTitle: string; rateNote: string; dailyTitle: string; noticesLabel: string; awardsLabel: string; tableLabel: string; awardsCol: string; medianCol: string; rangeCol: string; dateCol: string };
calculator: {
Expand Down Expand Up @@ -88,6 +91,17 @@ export const procurement: Record<ProcLocale, ProcurementCopy> = {
tableLabel: 'Show as a table',
awardsCol: 'Awards', medianCol: 'Median', rangeCol: 'p25–p75', dateCol: 'Date',
},
integrity: {
heading: 'Integrity checks',
lead: 'The same record, read for the questions an auditor asks. Each check runs nightly across every corporate bidder and winner, and every result shows the public records it rests on.',
blocks: [
{ title: 'Related bidders who meet in tenders', body: 'Two firms the corporate registry links — the same 대표이사 or the same registered address — that keep bidding on the same notices. A competition question for the buying agency, not a finding of wrongdoing; whether a shared name is one person is settled only by 법인등기.' },
{ title: 'Closed companies still bidding', body: 'A firm the National Tax Service (국세청) holds as closed that bid on, or won, a tender after its closing date — or a dormant firm still bidding. A status fact from the state set beside the tender record, not an inference.' },
{ title: 'Only what is published by name', body: 'A check counts priced bids and awards — the entries an agency publishes by name. Where an agency lists unplaced entrants anonymously, as in design competitions, they are left out, even when the open data carries them.' },
{ title: 'Companies only', body: 'Checks run on corporate (법인) records. Sole proprietors (개인사업자) are withheld: their records identify a person.' },
],
note: 'Every result is a question for the agency that holds the documents. None is an accusation.',
},
how: {
heading: 'How it is built',
body: 'A deterministic pipeline: collect the public procurement record nightly, normalize it, key it per lot and per company, flag implausible figures, and report — every output traceable to the published record it came from. Built on public and free-tier sources.',
Expand Down Expand Up @@ -188,6 +202,17 @@ export const procurement: Record<ProcLocale, ProcurementCopy> = {
tableLabel: '표로 보기',
awardsCol: '낙찰', medianCol: '중앙값', rangeCol: 'p25–p75', dateCol: '날짜',
},
integrity: {
heading: '청렴성 점검',
lead: '같은 기록을 감사의 관점에서 읽습니다. 모든 점검은 매일 밤 전체 법인 입찰·낙찰 업체를 대상으로 실행되며, 각 결과에는 근거가 된 공개 기록이 함께 표시됩니다.',
blocks: [
{ title: '같은 공고에서 반복해 만나는 관계 업체', body: '공시(DART) 정보상 대표이사 또는 등록 주소가 같은 두 업체가 같은 공고에 반복해서 투찰하는 경우입니다. 발주기관이 확인할 경쟁성의 문제이며 위법 판단이 아닙니다. 같은 이름이 동일인인지는 법인등기부로만 확정됩니다.' },
{ title: '폐업 후에도 입찰하는 업체', body: '국세청이 폐업으로 관리하는 업체가 폐업일 이후 입찰에 참여하거나 낙찰받은 경우, 또는 휴업 중인 업체가 입찰한 경우입니다. 추정이 아니라 국세청 상태 정보와 입찰 기록을 나란히 놓은 사실입니다.' },
{ title: '이름이 공개된 참여만', body: '가격을 제출한 투찰과 낙찰만 셉니다. 설계공모처럼 발주기관이 미입상 참가자를 익명으로 공개하는 경우에는, 공개 데이터에 이름이 남아 있더라도 제외합니다.' },
{ title: '법인만', body: '점검은 법인 기록에만 적용합니다. 개인사업자 정보는 개인을 식별할 수 있어 제외합니다.' },
],
note: '모든 결과는 관련 문서를 보유한 발주기관이 확인할 질문이며, 어떤 결과도 고발이 아닙니다.',
},
how: {
heading: '어떻게 만드는가',
body: '결정론적 파이프라인: 매일 밤 공공조달 기록을 수집하고, 정규화하고, 건별·업체별로 키를 부여하고, 비정상 수치를 표시하고, 보고합니다 — 모든 출력은 그것이 나온 공개 기록으로 추적됩니다. 공개 및 무료 데이터 소스로 구축했습니다.',
Expand Down
Loading