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 one or more files/folders into a single timestamped zip file. |
|
Load a Project from a folder path. |
|
Create a new Project from a configuration dictionary or file. |
|
Archive |
Classes
|
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/.jsonfile.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 toNone.source(str): Source reference. Defaults to empty string.description(str): Project description. Defaults to empty string.sources(str): Path to a.yaml/.toml/.jsonsources config to copy intoadmin/config/. Defaults toNone.
Note metadata keys (any field accepted by the project note template):
title,subtitle,subject,categorystatus— defaults to"on going"if not providedaliases— defaults to["{name} project", "Project {name}"]activity_id,service_id,professional_idcontractor,contractor_person,client,client_person,provider,provider_persondate_start,date_end,revenue_expected
- Raises:
FileNotFoundError – If a file path is given for
configorsourcesand the file does not exist.ValueError – If any required key is missing or the project folder already exists.
- Returns:
A new
losalamos.Projectinstance 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 intosourcesautomatically. Overlay files inadmin/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,
branchis set to the intermediate folder name so that remote-folder mirroring preserves the fullvault/branch/namelayout.
- Returns:
A new
losalamos.Projectinstance.- 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
folderdoes not exist as a directory.FileNotFoundError – If any path in
sourcesdoes 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
sourcesand publish the result under a managedhistory/lateststructure.Builds (or reuses) a folder layout at
folder/name/containing two subfolders,historyandlatest. The new archive is built directly intolatestfirst; only after it is successfully created is the previously-existing zip (if any) moved intohistory. This ordering ensures that if archiving fails,lateststill 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 fromarchive(), 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
folderdoes not exist as a directory.FileNotFoundError – If any path in
sourcesdoes 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
latestsubfolder is created; on subsequent calls the previous zip is rotated intohistorybefore 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:
FileSysProject filesystem abstraction.
Extends
FileSyswith metadata loading, external note resolution, and TeX overlay generation.The
sourcesdict maps note categories to lists of directory paths scanned when resolving contractors, clients, and services. It is auto-populated fromadmin/config/sources.toml(also.yamlor.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 thefiles_overlayparameter ofadd_document().- load_data()[source]#
Initialize internal project data.
Creates a dataframe describing the default project folder structure based on
SUBFOLDERSand assigns it toself.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 intoadmin/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 resolvesfolder_base,folder_root, andmain_note_pathaspathlib.Pathobjects whenfolder_baseis 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_pathand stores the resultingNoteProjectinself.main_note.
- get_contractor_person()[source]#
Return the contractor’s individual representative 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
.mdfile inself.sources["organizations"]first, then inself.sources["persons"]("sapiens"also accepted for backward compatibility). On success, stores the loaded note inself.contractorand the resolved path inself.contractor_path.sourcesis populated automatically fromadmin/config/sources.tomlon 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_personname from the project’s main note metadata and searchesself.sources["persons"]("sapiens"also accepted for backward compatibility) for a matching.mdfile. Ifself.contractorhas not been loaded yet, callsload_contractor()first. On success, stores the loaded note inself.contractor_personand the resolved path inself.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
.mdfile inself.sources["organizations"]first, then inself.sources["persons"]("sapiens"also accepted for backward compatibility). On success, stores the loaded note inself.clientand the resolved path inself.client_path.sourcesis populated automatically fromadmin/config/sources.tomlon 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_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
.mdfile inself.sources["organizations"]first, then inself.sources["persons"]("sapiens"also accepted for backward compatibility). On success, stores the loaded note inself.providerand the resolved path inself.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_personname from the project’s main note metadata and searchesself.sources["persons"]("sapiens"also accepted for backward compatibility) for a matching.mdfile. Ifself.providerhas not been loaded yet, callsload_provider()first. On success, stores the loaded note inself.provider_personand the resolved path inself.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_idfrom the project’s main note metadata and searchesself.sources["services"]for a matching.mdfile. The service name is expected in the note’sabstractfield. On success, stores the loaded note inself.serviceand the resolved path inself.service_path.sourcesis populated automatically fromadmin/config/sources.tomlon 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.texoverlay from the project’s service data.Loads
serviceif not yet set. The service ID is read from the project note’sservice_idfield; the service name is read from theservicenote’sabstractfield.- 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.tomlin the project’sadmin/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
abstractmetadata 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 inplaceholderswith 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. Ifnamealready 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/templatesfolder.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_filedoes not exist.- Returns:
Path to the written overlay file.
- Return type:
pathlib.Path
- make_overlay_project()[source]#
Create a
project.texoverlay populated from the project’s main note.Fills only the fields that are known at the project level:
[Document Field]— thesubjectmetadata 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.textemplate 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.texoverlay from the project’s contractor data.Loads
contractorif not yet set. When the contractor is an organization, also loadscontractor_personas the representative. Delegates tomake_overlay_file()using the built-inparty_b.textemplate.Two scenarios are handled automatically:
Contractor is an organization —
contractoris aNoteOrganization;contractor_personprovides the representative’s fields. Both must be resolvable fromself.sources.Contractor is an individual —
contractoris aNotePerson; 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 Consultoriaandcontractor_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(nocontractor_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.texoverlay from the project’s client data.Loads
clientif not yet set. The client note can be either an organization or an individual — the mapping follows the same rules asmake_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 viamake_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 toadd_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 aNonevalue (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.texpair 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
.mdfile fornote_type: assetfront-matter and collectsasset_id,asset_type,name, andasset_file(with wiki-link and quote wrappers stripped). Rows are sorted byasset_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/orbudget/outflows/.The target folder is chosen from direction:
"inflow"writes tobudget/inflows/and"outflow"tobudget/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"whenNone.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:
- get_transfers() pandas.DataFrame[source]#
Return a DataFrame of all transfer notes found under the project root.
Scans every
.mdfile fornote_type: transferfront-matter and collects the transfer schema fields. Rows are sorted byname.- 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 atinputs/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’sadmin/config/sources.toml. The following overlays are applied when present inadmin/config/overlays/:project.tex→definitions/project.texparty_b_contractor.tex→definitions/party_b.tex
- Parameters:
config (dict or None) – Optional config dict forwarded to
apply_config(). When provided, rewritespartials/services-invoice.texfrom the services list and invoice settings in the dict.- Returns:
The newly created invoice document instance.
- Return type:
- 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 standardadmin/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 (excludingmain.tex) become file overlays on the receipt.config (dict or None) – Optional config dict forwarded to
apply_config(). When provided, rewritespartials/services-receipt.texfrom 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:
- build_invoice(file_id)[source]#
Compile a previously created invoice to PDF.
Reads
\DocVersionfromdefinitions/project.tex, compiles vialatexmkwith cleanup, and places the result atbudget/inflows/INVOICE_{project}_{file_id}_{version}.pdf. Updates theasset_filefield 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.texis 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
\DocVersionfromdefinitions/project.tex, compiles vialatexmkwith cleanup, and places the result atbudget/inflows/RECEIPT_{project}_{file_id}_{version}.pdf. Updates theasset_filefield 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.texis 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 atinputs/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’sadmin/config/sources.toml. The following overlays are applied when present inadmin/config/overlays/:project.tex→definitions/project.texparty_b_contractor.tex→definitions/party_b.tex
- Returns:
The newly created proposal document instance.
- Return type:
- build_proposal(file_id)[source]#
Compile a previously created proposal to PDF.
Reads
\DocVersionfromdefinitions/project.tex, compiles vialatexmkwith cleanup, and places the result atadmin/proposals/PROPOSAL_{project}_{file_id}_{version}.pdf. Updates theasset_filefield 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.texis 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 atoutputs/REPORT_{project}_{file_id}.md. No compilation is performed.The template directory is read from
sources["templates"]["documents"]["report"]in the project’sadmin/config/sources.toml. The following overlays are applied when present inadmin/config/overlays/:project.tex→definitions/project.texparty_b_contractor.tex→definitions/party_b.tex
- Returns:
The newly created report document instance.
- Return type:
- build_report(file_id)[source]#
Compile a previously created report to PDF.
Reads
\DocVersionfromdefinitions/project.tex, compiles vialatexmkwith cleanup, and places the result atoutputs/REPORT_{project}_{file_id}_{version}.pdf. Updates theasset_filefield 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.texis 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"])