npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bc3

v1.1.0

Published

Zero-dependency TypeScript parser for FIEBDC-3/BC3 construction budget files (Presto, ARQUIMEDES, TCQ). Builds a type-safe hierarchical model for bills of quantities, cost databases, and construction project budgets.

Readme

BC3 — FIEBDC-3 Parser for TypeScript & Node.js

npm version npm downloads license TypeScript CI

BC3 is a zero-dependency TypeScript parser for FIEBDC-3 / BC3 construction budget files — the standard format exported by Presto, ARQUIMEDES, TCQ, and Spanish-speaking construction software for bills of quantities (BOQ), cost databases, and project budgets.

npm install bc3

Parse .bc3 files into a type-safe hierarchical model — navigate chapters, subchapters, decomposition trees, measurements, entities, specifications, IT/BIM codes, geographic cost overrides, and cost coefficients with lossless fidelity to the original file.

Why BC3

FIEBDC-3 (also called BC3 or Formato BC3) is the standard exchange format for construction budgets in Spain and Latin America. Software like Presto, ARQUIMEDES, and TCQ exports .bc3 files — but no open-source TypeScript/JavaScript library could parse them into a usable data structure. BC3 fills that gap.

  • Parse .bc3 files from any FIEBDC-3 edition (2002–2020) out of the box
  • Walk chapters, subchapters, and decompositions as a typed tree
  • Extract prices, measurements, BIM codes, cost overrides, entities, and coefficients from every record type in the corpus
  • Zero runtime dependencies — works in Node.js, Deno, and the browser
  • Lossless fidelity to the original file; no data discarded

Status

Stable release — 15 of 16 record types parsed, 100% of observed corpus types supported, 147 regression tests. 7/7 real-world BC3 files parse with 0 errors.

Current version: v1.0.0

Installation

npm install bc3

Quick Start

import { BC3 } from 'bc3';

const result = BC3.parse(bc3Text, { mode: 'lenient' });

if (result.document) {
  console.log(`Root concepts: ${result.document.roots.length}`);
  console.log(`Total concepts: ${result.document.conceptsByCode.size}`);
  console.log(`Diagnostics: ${result.diagnostics.length}`);

  // Walk the hierarchy
  result.document.walkTree((node, depth) => {
    console.log(
      `${'  '.repeat(depth)}${node.concept.codeNorm} — ${node.concept.summary}`,
    );
  });
}

Features

Parsing

  • 15 record-type parsers~V through ~G, plus UnknownRecordParser
  • Real-world corpus support — tested against 7 BC3 files from Presto, ARQUIMEDES, and TCQ spanning FIEBDC-3/2002–2020
  • Multiline ~D records — ARQUIMEDES continuation lines parsed correctly
  • Dotted child codesWORKER.1a, I.LT04.01 handled correctly in decompositions
  • Geographic cost overrides~O records with location/price pairs
  • Dual parsing modes'lenient' (collect diagnostics, continue) and 'strict' (fail on first error)
  • Encoding-agnostic — accepts UTF-16 strings; callers decode Latin-1 before calling BC3.parse()

Domain model

  • Hierarchy tree — chapters, subchapters, and items as ConceptNode instances with parent-child traversal
  • Multiple occurrences — concepts appearing in multiple branches tracked correctly
  • Decompositions — factor, performance (rendimiento), and percentage codes
  • Measurements — positions, totals, BIM IDs, dimensions
  • Metadata — document version, generator, dates, and properties from ~V
  • Cost coefficients — currency, rounding, overhead rates from ~K
  • Entities — companies, persons, and contacts from ~E
  • Specifications — pliegos sections from ~L
  • IT / BIM codes — BIM parameters, LCA environmental data from ~X
  • Thesaurus — keyword classification from ~A
  • Geographic overrides — regional cost adjustments from ~O
  • Diagnostics — warnings and errors with codes, messages, and record positions

Architecture

  • Zero runtime dependencies — pure TypeScript, no npm dependencies
  • ESM-native — dual CJS/ESM output via tsup
  • TypeScript strict modenoUncheckedIndexedAccess, noImplicitOverride
  • Builder pattern — clean separation between parsing and domain assembly
  • Strategy pattern — one parser class per record type, independently testable

API Reference

BC3.parse(input, options?)

Parses a BC3 text input and returns a structured result.

const result: ParseResult = BC3.parse(input: string, options?: {
  mode?: 'strict' | 'lenient';  // default: 'lenient'
});

interface ParseResult {
  document?: BC3Document;
  diagnostics: Diagnostic[];
}

Encoding: BC3 corpus files use ISO-8859-1 (Latin-1). Callers must decode before parsing:

import fs from 'node:fs';
const input = fs.readFileSync('file.bc3', 'latin1');
const result = BC3.parse(input, { mode: 'lenient' });

BC3Document

| Property | Type | Description | | -------------------------- | ------------------------------- | -------------------------------------- | | roots | ConceptNode[] | Root nodes of the hierarchy | | conceptsByCode | Map<string, ConceptNode> | All concepts, keyed by normalized code | | metadata | DocumentMetadata \| undefined | Version, generator, dates from ~V | | coefficients | Coefficients \| undefined | Currency, rounding, overhead from ~K | | costOverrides | Map<string, CostOverride> | Geographic price adjustments from ~O | | entities | Map<string, Entity> | Companies and contacts from ~E | | specificationsDictionary | Specification \| undefined | Pliegos sections from ~L | | itCodesDictionary | ITCodes \| undefined | IT/BIM/LCA codes from ~X | | diagnostics | Diagnostic[] | Warnings and errors |

| Method | Description | | ------------------------------------- | --------------------------------------------------------------------- | | getConcept(code) | Lookup a ConceptNode by normalized code | | walkTree(visitor) | DFS traversal with (node, depth, path) callback | | getHierarchySummary() | { totalNodes, rootNodes, maxDepth, nodesByDepth } | | getAllPathsToConcept(code) | All paths from roots to a concept | | getParentNodes(code) | All parent nodes of a concept | | getChildNodes(code) | Direct children | | getDecompositionInfo(parent, child) | { performance?, factor? } | | countConceptOccurrences(code) | Occurrence count in the tree | | getResourceHierarchy() | Concepts grouped by resource type (labor, machinery, materials, etc.) |

ConceptNode

| Property | Type | Description | | ---------------- | ---------------------------- | ---------------------------------------------- | | concept | Concept | Code, unit, summary, prices, dates, type, text | | children | ConceptNode[] | Direct children in hierarchy | | decompositions | Decomposition[] | Parent-child economic relationships | | measurements | Measurement[] | Associated measurements | | specification | Specification \| undefined | Pliego sections | | itCodes | ITCodes \| undefined | IT/BIM codes | | thesaurus | Thesaurus \| undefined | Keywords |

Examples

Navigate the hierarchy

const result = BC3.parse(bc3Text);
const doc = result.document;

if (doc) {
  const summary = doc.getHierarchySummary();
  console.log(
    `Roots: ${summary.rootNodes}, Total: ${summary.totalNodes}, Depth: ${summary.maxDepth}`,
  );

  doc.walkTree((node, depth) => {
    console.log(
      `${'  '.repeat(depth)}${node.concept.codeNorm} — ${node.concept.summary}`,
    );
  });
}

Find concept occurrences

const paths = doc.getAllPathsToConcept('001010');
paths.forEach((path, i) => {
  console.log(
    `Path ${i + 1}: ${path.map((n) => n.concept.codeNorm).join(' → ')}`,
  );
});

const decompInfo = doc.getDecompositionInfo('300100', '001010');
if (decompInfo) {
  const child = doc.getConcept('001010')!;
  const price = child.concept.prices.at(-1)!;
  console.log(`Amount: ${price * (decompInfo.performance ?? 0)}`);
}

Parse a Latin-1 encoded file (Node.js)

import fs from 'node:fs';
import { BC3 } from 'bc3';

const input = fs.readFileSync('file.bc3', 'latin1');
const result = BC3.parse(input, { mode: 'lenient' });

// Check diagnostics
for (const d of result.diagnostics) {
  console.log(`[${d.level}] ${d.code}: ${d.message}`);
}

Parsing Modes

| Mode | Unknown records | Missing fields | Invalid values | Behavior | | ------------------- | --------------- | --------------- | --------------- | ------------------------- | | lenient (default) | Warn + skip | Warn + continue | Warn + continue | Always returns a document | | strict | Throw | Throw | Throw | Fails on first error |

Architecture & Documentation

| Topic | Document | | ------------------------ | ---------------------------------------------------------------------------------------- | | Architecture overview | docs/architecture/overview.md | | Module boundaries | docs/architecture/module-boundaries.md | | Design patterns | docs/architecture/design-patterns.md | | BC3 grammar | docs/parser/grammar.md | | Record parsers | docs/parser/record-parsers.md | | Parsing modes | docs/parser/parsing-modes.md | | Parser coverage | docs/parser/parser-coverage-matrix.md | | Hierarchy reconstruction | docs/parser/hierarchy-reconstruction.md | | Domain model | docs/domain/model.md | | Public API | docs/public-api.md | | Usage examples | docs/examples.md | | Development setup | docs/development/setup.md | | Roadmap | docs/development/work-to-issue-mapping.md | | ADRs | docs/decisions/index.md |

Project Workflow

  • Development on develop; production on main
  • Branch naming: feat/, fix/, test/, docs/, chore/ prefixed with issue number
  • Changesets for versioning; automated npm publish on merge to main
  • npm run ci gates all PRs: build + format check

Related Terms

| English | Español | | ---------------------------------- | ----------------------------------------- | | bill of quantities (BOQ) | presupuesto, mediciones | | cost database | base de precios, banco de precios, cuadro | | construction budget | presupuesto de obra, proyecto | | cost estimation | valoración, estimación de costes | | decomposition, work breakdown | descomposición, descompuestos | | FIEBDC-3, BC3, Formato BC3 | FIEBDC-3, BC3, Formato BC3 | | Presto, ARQUIMEDES, TCQ | Presto, ARQUIMEDES, TCQ | | TypeScript parser, Node.js library | librería TypeScript, parser Node.js | | concept tree, hierarchy | árbol de conceptos, jerarquía | | overhead, indirect costs | costes indirectos, gastos generales | | geographic cost override | cuadro de precios geográfico | | measurements, quantities | cantidades, líneas de medición | | price list, unit prices | lista de precios, precios unitarios |

License

MIT © Igor HC