@lacspace/gov-notices
v1.1.0
Published
Parse Nepali government and university notice boards into clean, dated, tagged items for exam and results feeds. Site adapters for PSC (Lok Sewa) notices, written results and recommendations, NEB, SEE (OCE Sanothimi) notices and results, TSC notices and r
Maintainers
Readme
@lacspace/gov-notices
Turns the notice boards of Nepali government bodies and universities into clean items you can put in a feed:
- a title, with a flag for whether it is in Nepali, English or both;
- the date in AD and Bikram Sambat, plus the date exactly as the site printed it;
- an absolute URL, and the PDFs, images and documents attached to the notice;
- tags such as
result,exam,schedule,admit-card,vacancy,syllabusandrecommendation.
It has adapters for the boards listed below, including dedicated results lists for PSC, TSC and SEE, and a generic finder for any other notice page. It has no dependencies, runs anywhere fetch does, and uses no AI or API keys.
npm i @lacspace/gov-noticesChanges in 1.1.0
All changes are additive. Existing ids, fields and the one-request default are unchanged.
- Cut titles. CTEVT, among others, shortens long titles on its list ("Call for Papers for Journal of Technical and Vocational E..."). The full title is not anywhere in the list markup, so:
- every adapter now prefers a link's
title/data-titleattribute when the visible text ends with "…" or "..."; - a title that is still cut is flagged
truncated: true; fetchNotices(id, { details: true, maxDetails: 5, knownIds })reads the detail pages of cut items it has not seen before and fills in the full title. It is opt-in, sequential and capped.completeTitles(notices, opts)does the same for notices from any source, andparseDetail(html, url)reads one detail page.
- every adapter now prefers a link's
- Undated items. An item whose list card shows no date now has
undated: true(datestays undefined). Withdetails: true, its date comes from the detail page. For example, the DoTM homepage card for the class B written-exam questions has an empty date slot, and its detail page says१९ असोज, २०८३. - DoTM.
SOURCES.dotmnow reads/category/latest-news/, the site's dated "ताजा समाचार" list. Before, it read the homepage blocks, where some cards have no date. - New results sources:
psc-results(written exam results),psc-recommendations(final recommendations),tsc-results(TSC category 73) andsee-results(SEE's प्रकाशन list, where results are posted). See Site status. - Tags. There is a new
recommendationtag (सिफारिस, "recommend"). PSC results items carryresultfrom the feed itself, soisResult()is true for everypsc-resultsandpsc-recommendationsitem. A PSC reference such as "विज्ञापन नं. १२०२०" is no longer taggedvacancy. A real call for applications (दरखास्त, "विज्ञापन" without a number) still is. - The default User-Agent is now
lacspace-gov-notices/1.1.
Quick start: "results out" alerts
import { fetchNotices, newSince, isResult } from "@lacspace/gov-notices";
// Load these from your store. Use an empty set on the first run.
const seen: Set<string> = await loadSeenIds();
const prev = await loadValidators("neb"); // { etag?, lastModified?, contentHash? }
const r = await fetchNotices("neb", { etag: prev.etag, lastModified: prev.lastModified });
// For a results feed, poll "psc-results", "psc-recommendations", "tsc-results", "see-results" and "neb" the same way.
if (!r.notModified && r.contentHash !== prev.contentHash) {
for (const n of newSince(r.notices, seen)) {
if (isResult(n)) await alert(`Results out: ${n.title} (${n.dateBs}) ${n.url}`);
seen.add(n.id);
}
}
await saveValidators("neb", { etag: r.etag, lastModified: r.lastModified, contentHash: r.contentHash });
await saveSeenIds(seen);You can also parse a page you fetched yourself:
import { parseNotices } from "@lacspace/gov-notices";
parseNotices(html, { baseUrl: "https://tsc.gov.np/category/73/" });
// [{ id: "0928d0f6faad8846", sourceId: "tsc", title: "बढुवा नतिजा पुनरावलोकनसम्बन्धी सूचना (…)", titleLang: "ne",
// date: "2026-09-30", dateBs: "2083-06-14", dateRaw: "असोज १४, २०८३",
// url: "https://tsc.gov.np/content/4216/…", attachments: [{ url: "https://…/Pokhara_2tqbqfz.pdf", type: "pdf" }],
// category: "नतिजा", tags: ["result"] }, …]API
parseNotices(body, { baseUrl, sourceId?, now? }): parses a page intoNotice[].bodyis HTML, or JSON for the sites whose list comes from an API.- The adapter is chosen by
sourceId, then by the host ofbaseUrl. Anything else goes to the generic finder. - Relative links resolve against
baseUrl, and duplicates are removed.
fetchNotices(sourceIdOrUrl, opts?): makes one GET and parses the response. It never follows pagination. It returns{ status, notModified, notices, etag?, lastModified?, contentHash?, url, source? }.fetch: your own fetch implementation. Defaults to the global one.userAgent: defaults tolacspace-gov-notices/1.1 (+https://developer.lacspace.com/packages/gov-notices). Pass a browser UA if a site refuses bots.timeoutMs: defaults to 20000.maxBytes: defaults to 5 MB.headers: extra request headers.etag/lastModified: sent asIf-None-Match/If-Modified-Since. A 304 comes back asnotModified: true, nothing is parsed andnoticesis[].- Non-2xx responses come back with their
statusand no notices. Network errors throw. details(defaultfalse): after the list, read the detail pages of items whose title is cut or that have no date, and complete them (seecompleteTitles). Related options aremaxDetails(default 5),knownIds(ids you already hold, which are never fetched) anddetailDelayMs(default 1000). The result then carriesdetailsFetched.
completeTitles(notices, opts?): fills in cut titles and, unlessdates: false, missing dates from each notice's detail page. Notices are updated in place and the same array is returned.- It only fetches items that have an HTML detail URL (never a file), that are cut or undated, and whose id is not in
knownIds. - It makes at most
maxDetailsrequests (default 5), one at a time,delayMs(default 1000) apart. - Ids never change. Files linked from the detail page are added to
attachments. A title the page cannot complete keepstruncated: true, and a failed request is skipped. - It also takes
fetch,userAgent,timeoutMs,maxBytesandheaders.
- It only fetches items that have an HTML detail URL (never a file), that are cut or undated, and whose id is not in
parseDetail(html, url): returns{ title?, titles, date?, dateBs?, dateRaw?, attachments }for a notice's own page.titlesholds the heading,og:titleand<title>, best first.- The date comes from the page's date slot, such as GIWMS
.meta__group .dateor CTEVT "published on". - Dates elsewhere on the page, such as header sliders, are ignored.
needsDetail(notices, { knownIds?, dates? }): the notices a detail pass would fetch, in list order, before themaxDetailscap.completeTitle(cut, candidates),isTruncated(title): the title-completion helpers.SOURCES: the registry. Each entry has{ id, name, nameNe, url, feedUrl?, headers?, kind, adapter }.urlis the page a person would open.feedUrlis whatfetchNoticesactually reads, for sites that render their list with JavaScript.parseBsDate(s): returns{ bs, ad }ornull. Reads a BS date anywhere ins.parseDate(s): returns{ ad, bs?, raw, calendar }ornull. Reads a date in either calendar (see Dates below).dedupe(notices): removes duplicates by URL, then by normalised title + date. The first copy is kept and the duplicates' attachments are merged into it.newSince(notices, prevIds): returns the notices whoseidis not inprevIds.tag(notice)/isResult(notice): tags inferred from the title and category. Tags are already filled in on parsed notices.conditional(prev): buildsIf-None-Match/If-Modified-Sinceheaders from{ etag, lastModified }, for when you fetch the page yourself.cleanTitle(s),titleLang(s): the title helpers used internally.genericParse(doc),parseHTML(html),queryAll/queryOne,ADAPTERS,adapterFor(host): the building blocks, for writing your own adapter.
Notice fields
| Field | Type | Notes |
|---|---|---|
| id | string | 16 hex chars. A stable hash of sourceId + url, or of sourceId + title + date when there is no URL. |
| sourceId | string | The SOURCES id, else the host. |
| title | string | Whitespace collapsed. "New"/"नयाँ" badges, a trailing "(PDF)" and leading serials (1., १., क्र.सं.) are stripped. |
| titleLang | 'ne' \| 'en' \| 'mixed' | ne when at least 60% of the letters are Devanagari, en when at most 10% are. |
| date | string? | AD, YYYY-MM-DD. |
| dateBs | string? | BS, YYYY-MM-DD, ASCII digits. |
| dateRaw | string? | The date text as printed, e.g. असोज ८, २०८३ or Sep 15, 2026. |
| url | string? | Absolute. The notice's own page, or else its first file. |
| attachments | { url, type, label? }[] | type is pdf, image, doc or other, from the file extension or the site's media type. |
| category | string? | The site's tab, section or category label. |
| tags | string[]? | result, exam, schedule, admit-card, vacancy, syllabus, recommendation. |
| undated | true? | Set when the list showed no date (date is undefined). |
| truncated | true? | Set when the title still ends with "…" or "..." because the site cut it. |
Site status
Probed on 2026-10-05 (BS 2083-06-19) from outside Nepal. "Live items" is the number of notices fetchNotices parsed from the live page that day.
| id | Body | Notice list | Reachable | Adapter | Fixture tested | Live items | Date format seen |
|---|---|---|---|---|---|---|---|
| psc | Public Service Commission (लोक सेवा आयोग) | https://psc.gov.np/category/notice (read from the JSON feed /front/category/notice) | yes | psc (JSON) | yes | 10 | upload_date AD + upload_date_bs |
| psc-results | PSC written exam results (लिखित नतिजा) | https://psc.gov.np/category/result/all (read from /front/branch-details/all/written_result) | yes | psc (JSON) | yes | 20 (all result) | date_upload AD + date_upload_bs |
| psc-recommendations | PSC recommendations, i.e. final results (सिफारिस) | https://psc.gov.np/category/recommended/all (read from /front/branch-details/all/recommendation) | yes | psc (JSON) | yes | 20 (all result) | date_upload AD + date_upload_bs |
| neb | National Examinations Board | https://neb.gov.np/ (homepage tabs: notices, results, exam routines, vacancy syllabus) | yes | neb | yes | 24 (10 result) | प्रकाशित मिति २०८३ आश्विन १५ गते |
| see | Office of the Controller of Examinations, Sanothimi (SEE) | https://see.gov.np/category/notice/ | yes | giwms | yes | 4 | १ असोज, २०८३ |
| see-results | SEE results and publications (प्रकाशन) | https://see.gov.np/category/publication/ | yes | giwms | yes | 5 (4 result) | २२ साउन, २०८३ |
| tsc | Teacher Service Commission | https://tsc.gov.np/category/72/ (notices) | yes | giwms | yes | 6 | असोज ८, २०८३, बिहिबार १५:६ |
| tsc-results | TSC results and recommendations (नतिजा) | https://tsc.gov.np/category/73/ | yes | giwms | yes | 6 (all result) | असोज १४, २०८३, बुधबार १७:१० |
| mec | Medical Education Commission | https://mec.gov.np/np/category/notice | TLS certificate expired: Node's fetch refuses it, curl -k works | mec | yes | 0 (fetch failed) / 10 from a saved copy | १४ आश्विन २०८३, बुधबार |
| ctevt | CTEVT | https://ctevt.org.np/documents/list/notice-board | yes | ctevt | yes | 13 (3 truncated; all 3 completed with details: true) | Sep 15, 2026 (AD) |
| tuexam | TU Office of the Controller of Examinations | https://tuexam.edu.np/ (unverified) | no (connection timed out from outside Nepal) | generic | no | 0 | n/a |
| nec | Nepal Engineering Council | https://nec.gov.np/notices (read from the tRPC feed notice.getNoticesPublic) | yes | nec (JSON) | yes | 10 (3 result) | ISO timestamp, read as the Nepal (UTC+05:45) day |
| nmc | Nepal Medical Council | https://nmc.org.np/latest-notice | yes, but the list is rendered over a Blazor Server socket and is empty in the HTML | generic | no | 0 | n/a |
| dotm | Department of Transport Management | https://dotm.gov.np/category/latest-news/ (the dated "ताजा समाचार" list; 6 per page) | yes (rate-limits bursts with 403) | giwms | yes | 6 (all dated) | १९ असोज, २०८३ |
Results lists that do not have their own source, as of the probe day:
- NEB: the homepage "नतिजा प्रकाशन" tab is already in
neb, with thatcategory. Its "more" page (/result-publish) returns HTTP 500, and/vacancy-resultsdoes too. - CTEVT:
ctevt.org.np/documents/list/resultsexists but is empty. Exam results are on the examination office's portal (itms.ctevt.org.np:5580/notices/result), whose list loads from an AJAX endpoint that returned HTTP 500 to every request. - NEC: there is no results category. The feed's
categoryIdfilter is ignored server-side, so results come throughnec(taggedresult). - PSC:
/front/category/resultis a 404. The results lists are thebranch-detailsfeeds used above.
The giwms adapter covers the shared Government Integrated Website Management System template, including its category tables and card grids. Many other *.gov.np sites use the same template, so it is worth trying it on any GIWMS page with fetchNotices(url).
None of these sites sent ETag or Last-Modified on the probe day. Use contentHash together with newSince to detect changes. Conditional headers are still sent whenever you pass validators.
For mec you can pass your own fetch. In Node that can be an undici Agent with relaxed TLS for that one host, which is your call. The library never turns off certificate checks.
Dates
Digits: Devanagari (
०-९) and ASCII digits are both read.Numeric forms:
/,-and.all work as separators.parseBsDate("२०८२/०६/१८"),parseBsDate("2082.6.18")andparseBsDate("2082-06-18")all return{ bs: "2082-06-18", ad: "2025-10-04" }.Month names: written as
YYYY Month D,Month D, YYYYorD Month YYYY, in Roman or Devanagari. Each month accepts several spellings:| Month | Spellings | |---|---| | 1 | Baisakh / Baishakh / बैशाख / वैशाख | | 2 | Jestha / Jeth / जेठ / जेष्ठ | | 3 | Asar / Ashadh / असार / आषाढ | | 4 | Shrawan / Saun / साउन / श्रावण | | 5 | Bhadra / Bhadau / भदौ / भाद्र | | 6 | Ashwin / Asoj / असोज / आश्विन | | 7 | Kartik / कात्तिक / कार्तिक | | 8 | Mangsir / मंसिर / मङ्सिर / मार्गशीर्ष | | 9 | Poush / Push / पुस / पौष | | 10 | Magh / माघ | | 11 | Falgun / Fagun / फागुन / फाल्गुन | | 12 | Chaitra / Chait / चैत / चैत्र |
Trailing
गते, weekdays and times are ignored.Validation: the day is checked against the real month length from
@lacspace/nepali-date(bundled at build time), so२०८२/०६/३३returnsnull. BS years 1970–2086 are covered.BS or AD (
parseDate):- A numeric year from 2050 to 2100 is read as BS. BS 2050 began in 1993, so a notice dated
2083-06-15is BS. - A year from 1990 to 2049 is read as AD (
2025-10-04). - English month names are AD (
Sep 15, 2026,5 October 2026). Nepali month names are BS.
- A numeric year from 2050 to 2100 is read as BS. BS 2050 began in 1993, so a notice dated
JSON feeds:
pscalready gives both dates.necgives a UTC timestamp, which is converted to the calendar day in Nepal.
Being polite
fetchNoticesmakes exactly one request per call and never crawls pagination, unless you passdetails: true. With it, the call makes at mostmaxDetails(default 5) more requests, one at a time and a second apart, and only for items that are cut or undated and not inknownIds. Pass the ids you already stored asknownIdsso old items are never fetched again. Run it once per site per polling cycle; every 15–30 minutes is plenty for exam and results news.- Send
etag/lastModifiedback on the next call, and comparecontentHashto skip unchanged pages. - The default User-Agent identifies the library and links to its documentation. Only switch to a browser UA when a site refuses the default.
- Some GIWMS hosts answer bursts of requests with 403, so don't fetch several pages from one host in parallel.
Notices are public government information; check each site's terms before republishing.
Licence
Lacspace Free Licence v1.0: free for personal and commercial use.
