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 bc3Parse .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.
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
.bc3files 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
Stable release — 15 of 16 record types parsed, 100% of observed corpus types supported, 179 regression tests. 7/7 real-world BC3 files parse with 0 errors. Includes getSummary() for record counts, concept type distribution, and diagnostic breakdowns.
Current version: v1.1.0
npm install bc3import { 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}`,
);
});
}- 15 record-type parsers —
~Vthrough~G, plusUnknownRecordParser - 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 codes —
WORKER.1a,I.LT04.01handled correctly in decompositions - Geographic cost overrides —
~Orecords 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()
- Hierarchy tree — chapters, subchapters, and items as
ConceptNodeinstances 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
- Zero runtime dependencies — pure TypeScript, no npm dependencies
- ESM-native — dual CJS/ESM output via tsup
- TypeScript strict mode —
noUncheckedIndexedAccess,noImplicitOverride - Builder pattern — clean separation between parsing and domain assembly
- Strategy pattern — one parser class per record type, independently testable
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' });
| 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.) |
| 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 |
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}`,
);
});
}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)}`);
}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}`);
}| 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 |
| 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 |
- Development on
develop; production onmain - Branch naming:
feat/,fix/,test/,docs/,chore/prefixed with issue number - Changesets for versioning; automated npm publish on merge to
main npm run cigates all PRs: build + format check
| 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 |
MIT © Igor HC