losalamos.project#

Project management and filesystem initialization utilities.

Defines Project and the convenience functions new_project() and load_project() for working with projects organized around a fixed folder layout (admin/, inputs/, outputs/, budget/).

Each project carries a main Markdown note with metadata (title, client, contractor, service, etc.). An optional sources configuration connects the project to external note libraries and drives automatic generation of the TeX definition overlays (party_a.tex, party_b.tex, project.tex, service.tex) consumed by document templates.

Functions

archive(sources, folder, name[, ...])

Archive one or more files/folders into a single timestamped zip file.

load_project(project_folder[, vault])

Load a Project from a folder path.

new_project(config)

Create a new Project from a configuration dictionary or file.

publish(sources, folder, name[, ...])

Archive sources and publish the result under a managed history/latest structure.

Classes

Project([name, alias])

Project filesystem abstraction.

losalamos.project.new_project(config)[source]#

Create a new Project from a configuration dictionary or file.

Builds the project folder structure, installs the main Markdown note populated from config, copies the sources config file if one is provided, then reloads the project so that all metadata and overlays are current on return.

Danger

This method overwrites all existing default files.

Parameters:

config (dict, str, or pathlib.Path) –

Project configuration. Either a mapping or a path to a .yaml/.toml/.json file.

Required keys:

  • folder_base (str): Directory in which the project folder is created.

  • name (str): Project folder name.

Filesystem keys (not written to the note):

  • alias (str): Short identifier. Defaults to None.

  • source (str): Source reference. Defaults to empty string.

  • description (str): Project description. Defaults to empty string.

  • sources (str): Path to a .yaml/.toml/.json sources config to copy into admin/config/. Defaults to None.

Note metadata keys (any field accepted by the project note template):

  • title, subtitle, subject, category

  • status — defaults to "on going" if not provided

  • aliases — defaults to ["{name} project", "Project {name}"]

  • activity_id, service_id, professional_id

  • contractor, contractor_person, client, client_person, provider, provider_person

  • date_start, date_end, revenue_expected

Raises:
  • FileNotFoundError – If a file path is given for config or sources and the file does not exist.

  • ValueError – If any required key is missing or the project folder already exists.

Returns:

A new losalamos.Project instance with the main note and sources config installed.

Return type:

losalamos.Project

Example — inline dict
import losalamos

pj = losalamos.new_project(config={
    "folder_base": "C:/projects",
    "name": "Survey2026",
    "alias": "SV26",
    "title": "Environmental Survey 2026",
    "status": "planning",
    "sources": "/vault/sources.toml",
})
Example — TOML config file

Save a project-config.toml:

folder_base = "/home/user/projects"
name = "Survey2026"
alias = "SV26"
title = "Environmental Survey 2026"
status = "planning"
sources = "/vault/sources.toml"

Then create the project:

import losalamos

pj = losalamos.new_project(config="project-config.toml")
losalamos.project.load_project(project_folder, vault=None)[source]#

Load a Project from a folder path.

If admin/config/sources.toml (or .yaml/.json) exists in the project, it is parsed and merged into sources automatically. Overlay files in admin/config/overlays/ are then regenerated from the loaded metadata.

Parameters:
  • project_folder (str or Path) – Path to the project root folder.

  • vault (str or Path or None) – Optional path to the vault root. When provided and the project sits one level below vault, branch is set to the intermediate folder name so that remote-folder mirroring preserves the full vault/branch/name layout.

Returns:

A new losalamos.Project instance.

Return type:

losalamos.Project

Example
import losalamos

pj = losalamos.load_project(project_folder="path/to/project/folder")
losalamos.project.archive(sources: str | Path | List[str | Path], folder: str | Path, name: str, ignore_subfolders: bool = False, ignore_names: List[str] | None = None, ignore_patterns: List[str] | None = None) Path[source]#

Archive one or more files/folders into a single timestamped zip file.

Sources are merged into a shared tree at the zip root rather than being namespaced under separate top-level folders — equivalent to pasting each source folder into the same destination one after another. Same-named subfolders combine their contents non-destructively. A file present in only one source appears once in the result; a relative path present in more than one source raises an error rather than silently overwriting.

Parameters:
  • sources (str, pathlib.Path, or list) – A single path or a list of paths (files and/or folders) to merge and include.

  • folder (str or pathlib.Path) – Target folder where the zip file will be written. Must already exist.

  • name (str) – Base name for the archive (timestamp is appended).

  • ignore_subfolders (bool) – If True, only the top-level files of each source are archived; all subfolders (and their contents) are skipped. Applied independently per source.

  • ignore_names (list of str or None) – Exact folder or file names to exclude, anywhere in the tree (e.g. ["cache", "settings.txt"]). File names must include their extension.

  • ignore_patterns (list of str or None) – Glob-style patterns (* syntax) matched against the filename only (not the full path), e.g. ["*.tmp", "~*"]. Folders are matched the same way by their folder name and, if matched, their entire contents are excluded.

Raises:
  • NotADirectoryError – If folder does not exist as a directory.

  • FileNotFoundError – If any path in sources does not exist.

  • FileExistsError – If two sources resolve to the same relative path in the merged tree (conflicting file).

Returns:

Absolute path to the created zip file.

Return type:

pathlib.Path

Example

Import the package.

import losalamos

Archive two source folders into a single zip, skipping temporary files and a cache subfolder.

zip_path = losalamos.archive(
    sources=["path/to/data", "path/to/figures"],
    folder="path/to/output",
    name="myArchive",
    ignore_names=["cache"],
    ignore_patterns=["*.tmp", "~*"],
)

The returned path points to the timestamped zip file.

print(zip_path)
# path/to/output/myArchive_20260101T120000.zip
losalamos.project.publish(sources: str | Path | List[str | Path], folder: str | Path, name: str, ignore_subfolders: bool = False, ignore_names: List[str] | None = None, ignore_patterns: List[str] | None = None) Path[source]#

Archive sources and publish the result under a managed history/latest structure.

Builds (or reuses) a folder layout at folder/name/ containing two subfolders, history and latest. The new archive is built directly into latest first; only after it is successfully created is the previously-existing zip (if any) moved into history. This ordering ensures that if archiving fails, latest still holds the last good publish rather than being left empty or having prematurely rotated a valid zip away. Each zip keeps its own timestamped filename from archive(), so nothing is overwritten on rotation.

Parameters:
  • sources (str, pathlib.Path, or list) – A single path or a list of paths (files and/or folders) to merge and archive. See archive().

  • folder (str or pathlib.Path) – Output archive main folder. The managed structure is created at folder/name/. Must already exist.

  • name (str) – Base name for the archive and the managed subfolder under folder.

  • ignore_subfolders (bool) – See archive().

  • ignore_names (list of str or None) – See archive().

  • ignore_patterns (list of str or None) – See archive().

Raises:
  • NotADirectoryError – If folder does not exist as a directory.

  • FileNotFoundError – If any path in sources does not exist.

  • FileExistsError – If two sources resolve to the same relative path in the merged tree (conflicting file).

Returns:

Absolute path to the newly published zip file inside latest.

Return type:

pathlib.Path

Example

Import the package.

import losalamos

Publish two source folders to a managed output location. On the first call the latest subfolder is created; on subsequent calls the previous zip is rotated into history before the new one is written.

zip_path = losalamos.publish(
    sources=["path/to/data", "path/to/figures"],
    folder="path/to/output",
    name="myDeliverable",
    ignore_patterns=["*.tmp"],
)

The returned path points to the new zip inside latest.

print(zip_path)
# path/to/output/myDeliverable/latest/myDeliverable_20260101T120000.zip
class losalamos.project.Project(name='LosAlamosProject', alias='LAProj')[source]#

Bases: FileSys

Project filesystem abstraction.

Extends FileSys with metadata loading, external note resolution, and TeX overlay generation.

The sources dict maps note categories to lists of directory paths scanned when resolving contractors, clients, and services. It is auto-populated from admin/config/sources.toml (also .yaml or .json) each time the project is opened:

# admin/config/sources.toml
[folders.search]
organizations = ["/vault/organizations"]   # NoteOrganization notes
persons       = ["/vault/people"]          # NotePerson notes  ("sapiens" also accepted)
services      = ["/vault/services"]        # NoteBasic notes, matched by service_id

[folders.remote]                           # optional remote vault roots
documents = "/vault/documents"
data      = "/vault/data"

[templates.documents]                      # document template directories
invoice  = "/vault/templates/invoice"
proposal = "/vault/templates/proposal"

Overlays are written to admin/config/overlays/ and applied to document templates via the files_overlay parameter of add_document().

load_data()[source]#

Initialize internal project data.

Creates a dataframe describing the default project folder structure based on SUBFOLDERS and assigns it to self.data.

setup()[source]#

Set up the project folder structure and install default config templates.

Delegates folder and file setup to the parent FileSys.setup(), then copies any missing config templates into admin/config/ via _install_config_templates().

Danger

This method overwrites all existing default files (parent behaviour).

update()[source]#

Refresh derived attributes from the current configuration.

Calls the parent FileSys.update() and then resolves folder_base, folder_root, and main_note_path as pathlib.Path objects when folder_base is set. Loads the main project note only if the file already exists on disk, then attempts to regenerate overlay files via _update_overlays().

load_main_note()[source]#

Load the project’s main Markdown note from disk.

Reads self.main_note_path and stores the resulting NoteProject in self.main_note.

get_title()[source]#

Return the project title from the main note metadata.

get_subtitle()[source]#

Return the project subtitle from the main note metadata.

get_contractor()[source]#

Return the contractor name from the main note metadata.

get_contractor_person()[source]#

Return the contractor’s individual representative name from the main note metadata.

get_client()[source]#

Return the client name from the main note metadata.

load_contractor()[source]#

Load the contractor note from self.sources.

Reads the contractor name from the project’s main note metadata and searches for a matching .md file in self.sources["organizations"] first, then in self.sources["persons"] ("sapiens" also accepted for backward compatibility). On success, stores the loaded note in self.contractor and the resolved path in self.contractor_path.

sources is populated automatically from admin/config/sources.toml on project load; it can also be set manually before calling this method.

Raises:

FileNotFoundError – If no note matching the contractor name is found in any configured source.

Returns:

None

Return type:

None

load_contractor_person()[source]#

Load the individual contractor person note from self.sources.

Reads the contractor_person name from the project’s main note metadata and searches self.sources["persons"] ("sapiens" also accepted for backward compatibility) for a matching .md file. If self.contractor has not been loaded yet, calls load_contractor() first. On success, stores the loaded note in self.contractor_person and the resolved path in self.contractor_person_path.

Raises:

FileNotFoundError – If no note matching the contractor_person name is found in the configured person sources.

Returns:

None

Return type:

None

load_client()[source]#

Load the client note from self.sources.

Reads the client name from the project’s main note metadata and searches for a matching .md file in self.sources["organizations"] first, then in self.sources["persons"] ("sapiens" also accepted for backward compatibility). On success, stores the loaded note in self.client and the resolved path in self.client_path.

sources is populated automatically from admin/config/sources.toml on project load; it can also be set manually before calling this method.

Raises:

FileNotFoundError – If no note matching the client name is found in any configured source.

Returns:

None

Return type:

None

get_provider()[source]#

Return the provider name from the main note metadata.

get_provider_person()[source]#

Return the provider’s individual representative name from the main note metadata.

load_provider()[source]#

Load the provider note from self.sources.

Reads the provider name from the project’s main note metadata and searches for a matching .md file in self.sources["organizations"] first, then in self.sources["persons"] ("sapiens" also accepted for backward compatibility). On success, stores the loaded note in self.provider and the resolved path in self.provider_path.

Raises:

FileNotFoundError – If no note matching the provider name is found in any configured source.

Returns:

None

Return type:

None

load_provider_person()[source]#

Load the individual provider person note from self.sources.

Reads the provider_person name from the project’s main note metadata and searches self.sources["persons"] ("sapiens" also accepted for backward compatibility) for a matching .md file. If self.provider has not been loaded yet, calls load_provider() first. On success, stores the loaded note in self.provider_person and the resolved path in self.provider_person_path.

Raises:

FileNotFoundError – If no note matching the provider_person name is found in the configured person sources.

Returns:

None

Return type:

None

load_service()[source]#

Load the service note from self.sources.

Reads service_id from the project’s main note metadata and searches self.sources["services"] for a matching .md file. The service name is expected in the note’s abstract field. On success, stores the loaded note in self.service and the resolved path in self.service_path.

sources is populated automatically from admin/config/sources.toml on project load; it can also be set manually before calling this method.

Raises:

FileNotFoundError – If no note matching the service_id is found in the configured services sources.

Returns:

None

Return type:

None

make_overlay_service()[source]#

Create a service.tex overlay from the project’s service data.

Loads service if not yet set. The service ID is read from the project note’s service_id field; the service name is read from the service note’s abstract field.

Raises:

FileNotFoundError – If the service note cannot be found in self.sources.

Returns:

Path to the written overlay file.

Return type:

pathlib.Path

Example — sources via config file

Place sources.toml in the project’s admin/config/ folder:

[folders.search]
services = ["/vault/services"]   # folder containing <service_id>.md files

The file is read automatically on project load. Each service note must have an abstract metadata field with the display name:

# /vault/services/7891.md (front-matter excerpt)
abstract: "Environmental Assessment"

Then generate the overlay:

import losalamos

pj = losalamos.load_project("path/to/myProject")
path = pj.make_overlay_service()
# → <project>/admin/config/overlays/service.tex
Example — sources set manually
import losalamos

pj = losalamos.load_project("path/to/myProject")
pj.sources = {"folders": {"search": {"services": ["path/to/service/notes"]}}}

path = pj.make_overlay_service()
make_overlay_file(name, source_file, placeholders=None)[source]#

Create a populated overlay file from a template.

Reads source_file, replaces every key in placeholders with its corresponding value, and writes the result to the project’s overlay folder (admin/config/overlays/).

Parameters:
  • name (str) – Base name for the output file, without extension. The extension is taken from source_file. If name already carries the correct extension it is used as-is.

  • source_file (str or pathlib.Path) – Template to use as the source. An absolute path is used directly; a relative path is resolved from the package’s data/templates folder.

  • placeholders (dict or None) – Mapping of literal strings to find in the template to their replacement values. Each key is replaced everywhere it appears. Defaults to None (file copied verbatim).

Raises:

FileNotFoundError – If the resolved source_file does not exist.

Returns:

Path to the written overlay file.

Return type:

pathlib.Path

make_overlay_project()[source]#

Create a project.tex overlay populated from the project’s main note.

Fills only the fields that are known at the project level:

  • [Document Field] — the subject metadata field, with surrounding quotes and wiki-link brackets stripped automatically.

  • [Project ID] — the project name (self.name).

All remaining placeholders ([Document Type], [Certifier], etc.) are left unchanged in the output file for downstream overlays or manual editing.

Raises:

FileNotFoundError – If the built-in project.tex template is missing.

Returns:

Path to the written overlay file.

Return type:

pathlib.Path

Example
import losalamos

pj = losalamos.load_project("path/to/myProject")

path = pj.make_overlay_project()
# → <project>/admin/config/overlays/project.tex

The file can then be passed to add_document():

pj.add_document(
    document_type="invoice",
    files_overlay={"definitions/project.tex": path},
)
make_overlay_party_b_contractor()[source]#

Create a party_b_contractor.tex overlay from the project’s contractor data.

Loads contractor if not yet set. When the contractor is an organization, also loads contractor_person as the representative. Delegates to make_overlay_file() using the built-in party_b.tex template.

Two scenarios are handled automatically:

Contractor is an organizationcontractor is a NoteOrganization; contractor_person provides the representative’s fields. Both must be resolvable from self.sources.

Contractor is an individualcontractor is a NotePerson; all party-B fields (entity and representative) are derived from the same note.

Raises:

FileNotFoundError – If the contractor or person representative cannot be found in self.sources.

Returns:

Path to the written overlay file.

Return type:

pathlib.Path

Example — organization contractor with representative

The project note has contractor: AMA Consultoria and contractor_person: John Doe.

import losalamos

pj = losalamos.load_project("path/to/myProject")
pj.sources = {
    "folders": {
        "search": {
            "organizations": ["path/to/org/notes"],
            "persons": ["path/to/people/notes"],
        }
    }
}

path = pj.make_overlay_party_b_contractor()
# → <project>/admin/config/overlays/party_b_contractor.tex
# org fields from "AMA Consultoria.md", rep fields from "John Doe.md"

The resulting file can be passed directly to add_document():

pj.add_document(
    document_type="contract",
    files_overlay={"definitions/party_b.tex": path},
)
Example — individual contractor

The project note has contractor: John Doe (no contractor_person).

pj.sources = {"folders": {"search": {"persons": ["path/to/people/notes"]}}}

path = pj.make_overlay_party_b_contractor()
# entity and representative fields both come from "John Doe.md"
make_overlay_party_b_client()[source]#

Create a party_b_client.tex overlay from the project’s client data.

Loads client if not yet set. The client note can be either an organization or an individual — the mapping follows the same rules as make_overlay_party_b_contractor(). No separate client representative note is loaded; if the client is an organization and representative fields are needed, populate them manually via make_overlay_file() with a custom placeholder dict built from _build_party_b_placeholders().

Raises:

FileNotFoundError – If the client cannot be found in self.sources.

Returns:

Path to the written overlay file.

Return type:

pathlib.Path

Example — organization client

The project note has client: Big Boss Inc.

import losalamos

pj = losalamos.load_project("path/to/myProject")
pj.sources = {
    "folders": {
        "search": {
            "organizations": ["path/to/org/notes"],
            "persons": ["path/to/people/notes"],
        }
    }
}

path = pj.make_overlay_party_b_client()
# → <project>/admin/config/overlays/party_b_client.tex
# org fields from "Big Boss Inc..md"; representative fields are empty
Example — invoice targeting the contractor as party B

For an invoice, party B is typically the contractor, not the project client. Use make_overlay_party_b_contractor() and pass the result to add_document().

path = pj.make_overlay_party_b_contractor()

pj.add_document(
    document_type="invoice",
    files_overlay={"definitions/party_b.tex": path},
)
get_attribute(entry_key, clean_cref=True)[source]#

Read a metadata field from the project’s main note.

Parameters:
  • entry_key (str) – Metadata field name (e.g. "title", "client").

  • clean_cref (bool) – If True (default), strip Obsidian wiki-link brackets from the returned value.

Returns:

Field value with surrounding YAML quote characters stripped. Returns a bracketed placeholder (e.g. [TITLE]) when the field is absent or has a None value (empty YAML field).

Return type:

str

add_document(document_type, name=None, template_overlay=None, files_overlay=None, condensed=True, zip_export=False, compile_pdf=True, subfolder='inputs/documents', force_new=False)[source]#

Create a new document inside the project, optionally condensed into a flattened+split pair of files and/or compiled to PDF.

Parameters:
  • document_type (str) – Key into losalamos.documents.DOCUMENT_TYPES.

  • name (str or None) – Folder/registry name. Defaults to document_type. Must be non-empty and free of path separators.

  • template_overlay (str, pathlib.Path, or None) – Forwarded to Document.new().

  • files_overlay (dict or None) – Forwarded to Document.new().

  • condensed (bool) – If True, flatten+split into main.tex/preamble.tex via a temp staging dir. If False, keep the live template tree.

  • zip_export (bool) – Zip the condensed export (deletes the folder). Requires condensed=True; mutually exclusive with compile_pdf.

  • compile_pdf (bool) – Compile to PDF via DocumentTeX.to_pdf().

  • subfolder (str) – Project-relative target folder.

  • force_new (bool) – If True, force the new folder even if it already exists.

Raises:
  • ValueError – Unknown document_type; invalid name; or zip_export combined with condensed=False or compile_pdf=True.

  • FileExistsError – Target folder already exists.

Returns:

The reloaded document instance, or the zip Path when zip_export=True.

Return type:

losalamos.documents.Document or pathlib.Path

Example

Load an existing project.

import losalamos

pj = losalamos.load_project("path/to/myProject")

Add an invoice document. The template is condensed into a flat main.tex / preamble.tex pair and compiled to PDF.

doc = pj.add_document(
    document_type="invoice",
    name="invoice_client_2026",
    condensed=True,
    compile_pdf=True,
)

The returned instance is also registered in pj.documents.

print(pj.documents["invoice"])
get_assets() pandas.DataFrame[source]#

Return a DataFrame of all asset notes found under the project root.

Scans every .md file for note_type: asset front-matter and collects asset_id, asset_type, name, and asset_file (with wiki-link and quote wrappers stripped). Rows are sorted by asset_id.

Returns:

DataFrame with columns asset_id, asset_type, name, asset_file. Empty DataFrame when no assets exist.

Return type:

pandas.DataFrame

add_transfer(direction, date, account, value, status=None, commitment=None, recurrence=None, method=None, protocol=None, related_asset=None, payer=None, receiver=None, currency=None, domain=None, category=None, subcategory=None, file_bill=None, file_invoice=None, file_receipt=None, file_proof=None)[source]#

Create a new transfer note under budget/inflows/ or budget/outflows/.

The target folder is chosen from direction: "inflow" writes to budget/inflows/ and "outflow" to budget/outflows/.

Parameters:
  • direction (str) – Direction of the transfer. Must be "inflow" or "outflow".

  • date (str) – Due date of the transfer, e.g. "2026-09-02".

  • account (str) – Bank account code of the payer.

  • value (float or str) – Monetary value of the transfer.

  • status (str or None) – Transfer status. Typical values: "expected", "issued", "executed", "canceled", "prospected".

  • commitment (str or None) – Commitment group, e.g. "contracts", "lifestyle", "maintenance".

  • recurrence (str or None) – Recurrence in smart syntax, e.g. "1 mo", "1 yr", "non-recurrent".

  • method (str or None) – Payment method. Defaults to "manual" when None.

  • protocol (str or None) – Payment protocol, e.g. "pix", "deposit", "bill".

  • related_asset (str or None) – Optional link to a related asset note.

  • file_bill (str or None) – Wiki link to the bill file or note.

  • file_invoice (str or None) – Wiki link to the invoice file or note.

  • file_receipt (str or None) – Wiki link to the receipt file or note.

  • file_proof (str or None) – Wiki link to the proof-of-payment file or note.

  • payer (str or None) – Name or link to the payer party.

  • receiver (str or None) – Name or link to the receiver party.

  • currency (str or None) – Currency code, e.g. "BRL", "USD".

  • domain (str or None) – Open field for cross-classification.

  • category (str or None) – Category for hierarchy classification.

  • subcategory (str or None) – Subcategory for hierarchy classification.

Raises:

ValueError – If direction is not "inflow" or "outflow".

Returns:

The newly created transfer note.

Return type:

losalamos.notes.NoteTransfer

get_transfers() pandas.DataFrame[source]#

Return a DataFrame of all transfer notes found under the project root.

Scans every .md file for note_type: transfer front-matter and collects the transfer schema fields. Rows are sorted by name.

Returns:

DataFrame with columns name, date, direction, status, account, value, currency, commitment, recurrence, method, protocol, payer, receiver, domain, category, subcategory, related_asset, file_bill, file_invoice, file_receipt, file_proof. Empty DataFrame when no transfers exist.

Return type:

pandas.DataFrame

add_invoice(config=None)[source]#

Create a new invoice document.

The working tree is created at inputs/documents/INVOICE_{project}_{file_id}/ and the sidecar note at inputs/documents/INVOICE_{project}_{file_id}.md. No compilation or condensing is performed.

The template directory is read from sources["templates"]["documents"]["invoice"] in the project’s admin/config/sources.toml. The following overlays are applied when present in admin/config/overlays/:

  • project.texdefinitions/project.tex

  • party_b_contractor.texdefinitions/party_b.tex

Parameters:

config (dict or None) – Optional config dict forwarded to apply_config(). When provided, rewrites partials/services-invoice.tex from the services list and invoice settings in the dict.

Returns:

The newly created invoice document instance.

Return type:

losalamos.documents.Document

add_receipt(invoice_id=None, config=None)[source]#

Create a new receipt document inside inputs/documents/.

When invoice_id is provided, all files from the linked invoice folder (except main.tex) become file overlays, so the receipt inherits the invoice’s project definitions, party files, and any other configured assets. Without invoice_id, the standard admin/config/overlays/ files are applied instead.

The asset ID counter is shared with invoices, so IDs never collide across document types within the same project.

Parameters:
  • invoice_id (str or None) – Asset file ID of a previously created invoice, e.g. "F003". When provided, the invoice folder’s files (excluding main.tex) become file overlays on the receipt.

  • config (dict or None) – Optional config dict forwarded to apply_config(). When provided, rewrites partials/services-receipt.tex from the services list and invoice settings in the dict.

Raises:

FileNotFoundError – If invoice_id is given but the corresponding invoice folder does not exist.

Returns:

The newly created receipt document instance.

Return type:

losalamos.documents.Document

build_invoice(file_id)[source]#

Compile a previously created invoice to PDF.

Reads \DocVersion from definitions/project.tex, compiles via latexmk with cleanup, and places the result at budget/inflows/INVOICE_{project}_{file_id}_{version}.pdf. Updates the asset_file field in the sidecar note.

Parameters:

file_id (str) – Asset file ID assigned at creation, e.g. "F003".

Raises:

FileNotFoundError – If the invoice folder or main.tex is not found.

Returns:

Tuple of (pdf_path, zip_path).

Return type:

tuple[pathlib.Path, pathlib.Path]

build_receipt(file_id)[source]#

Compile a previously created receipt to PDF.

Reads \DocVersion from definitions/project.tex, compiles via latexmk with cleanup, and places the result at budget/inflows/RECEIPT_{project}_{file_id}_{version}.pdf. Updates the asset_file field in the sidecar note.

Parameters:

file_id (str) – Asset file ID assigned at creation, e.g. "F003".

Raises:

FileNotFoundError – If the receipt folder or main.tex is not found.

Returns:

Tuple of (pdf_path, zip_path).

Return type:

tuple[pathlib.Path, pathlib.Path]

add_proposal()[source]#

Create a new proposal document.

The working tree is created at inputs/documents/PROPOSAL_{project}_{file_id}/ and the sidecar note at inputs/documents/PROPOSAL_{project}_{file_id}.md. No compilation or condensing is performed.

The template directory is read from sources["templates"]["documents"]["proposal"] in the project’s admin/config/sources.toml. The following overlays are applied when present in admin/config/overlays/:

  • project.texdefinitions/project.tex

  • party_b_contractor.texdefinitions/party_b.tex

Returns:

The newly created proposal document instance.

Return type:

losalamos.documents.Document

build_proposal(file_id)[source]#

Compile a previously created proposal to PDF.

Reads \DocVersion from definitions/project.tex, compiles via latexmk with cleanup, and places the result at admin/proposals/PROPOSAL_{project}_{file_id}_{version}.pdf. Updates the asset_file field in the sidecar note.

Parameters:

file_id (str) – Asset file ID assigned at creation, e.g. "F005".

Raises:

FileNotFoundError – If the proposal folder or main.tex is not found.

Returns:

Tuple of (pdf_path, zip_path).

Return type:

tuple[pathlib.Path, pathlib.Path]

add_report()[source]#

Create a new report document.

The working tree is created at inputs/documents/REPORT_{project}_{file_id}/ and the sidecar note at outputs/REPORT_{project}_{file_id}.md. No compilation is performed.

The template directory is read from sources["templates"]["documents"]["report"] in the project’s admin/config/sources.toml. The following overlays are applied when present in admin/config/overlays/:

  • project.texdefinitions/project.tex

  • party_b_contractor.texdefinitions/party_b.tex

Returns:

The newly created report document instance.

Return type:

losalamos.documents.Document

build_report(file_id)[source]#

Compile a previously created report to PDF.

Reads \DocVersion from definitions/project.tex, compiles via latexmk with cleanup, and places the result at outputs/REPORT_{project}_{file_id}_{version}.pdf. Updates the asset_file field in the sidecar note.

Parameters:

file_id (str) – Asset file ID assigned at creation, e.g. "F006".

Raises:

FileNotFoundError – If the report folder or main.tex is not found.

Returns:

Tuple of (pdf_path, zip_path).

Return type:

tuple[pathlib.Path, pathlib.Path]

publish(targets, prefix, output_folder=None, surface=False)[source]#

Publish a versioned snapshot of selected directories to a managed output location.

Parameters:
  • targets (list) – A list of directory paths to be included in the snapshot.

  • prefix (str) – The string prefix used for naming the generated archive file.

  • output_folder (pathlib.Path) – [optional] The destination directory for the published archives.

  • surface (bool) – If True, target folders are placed at the zip root instead of preserving project subfolder structure.

Returns:

A dictionary containing the publication status, the resulting path, and metadata.

Return type:

dict

Note

The method performs directory validation, checks for publish frequency constraints based on self.publish_delta, and handles the rotation of the previous ‘latest’ archive into a history folder before promoting the new build.

Example

Load an existing project.

import losalamos

pj = losalamos.load_project("path/to/myProject")

Select the output folders to snapshot and trigger the publish. By default the archive lands under <project_root>/outputs/.

result = pj.publish(
    targets=[
        pj.folder_root / "outputs/public",
        pj.folder_root / "inputs/figures",
    ],
    prefix="myProject_delivery",
)

Inspect the result dictionary to confirm publication and retrieve the archive path.

if result["published"]:
    print(result["archive"])
else:
    print("Skipped:", result["reason"])