Chapter 1 · Purpose, scope and intake
A governed project record: from request to approved project
A project tool is only a list of tasks until it also knows who asked for the work, why it was chosen, who pays, who is accountable and what stage it is in. This chapter says what the module builds, what it leaves to its neighbours, and how a request becomes an approved project.
20 min14 tables11 capabilities8 project types
By the end of this chapter you can
- Say what the project module replaces and which work it leaves to other modules.
- Name the fourteen prj_ tables and the eight project types, and place a request, a project and a gate in their statuses.
- Score a request with a weighted model and say who decides it.
What the module does
The module plans and controls improvement, new product introduction, shutdown and capital projects. It takes a request through intake and portfolio scoring, builds a work breakdown structure with all four dependency types and a critical path, measures slip against baselines, tracks resources and timesheets, keeps budgets with commitments and earned value, runs the RAID log, punch lists and commissioning checklists, and produces status reports. The module specification names the tools it replaces: Smartsheet, Asana, monday.com, Microsoft Project, Wrike, ClickUp and Jira for work management, and Oracle Primavera P6 and Procore for capital projects. Roundups of planning software, such as the one the specification cites, list tools of this kind [3].
Project management practice is described in the PMI PMBOK Guide (7th edition) and its Practice Standard for Scheduling [2], and ISO 21502:2020 gives guidance on project, programme and portfolio management [1]. The module does not copy either document. It uses them as the vocabulary a project manager already knows, so that a record means what the manager expects.
Fourteen tables on the shared spine
Every table in the module carries the standard columns every platform table has: id, tenant_id, created_at, created_by, updated_at, updated_by, row_version, archived_at and ext. The module has no people table, site list or calendar of its own: it points at core_person, core_site and core_calendar. Its own tables share the prefix prj_.
The tables below define the three that start the module. Types are written as in the specification: text, int, num (a decimal number), bool, date, ts (a timestamp with time zone), uuid, json and money (stored as integer cents, converted only when shown). An arrow means a foreign key to that table, and req means required. Look at the allowed values before you design anything: they are the database's check constraints, and your agent builds from them in Session 2.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_portfolio | A portfolio or programme that groups projects | name text req, unique per tenant; owner_id → core_person; scoring_model json |
| prj_request | A project intake request with its business case and score | request_no text req, unique per tenant; title text req; problem_statement text; benefits json; est_cost money; est_benefit money; scores json; total_score num; requested_by → core_person req; status text req: submitted, under_review, approved, rejected or converted; decided_by → core_person; project_id → prj_project |
| prj_project | The project itself | code text req, unique per tenant; name text req; portfolio_id → prj_portfolio; project_type text req: capex, improvement, kaizen, npi, it, shutdown, customer or regulatory; status text req: proposed, approved, planning, executing, on_hold, closing, closed or cancelled; gate text: idea, define, plan, execute or close; sponsor_id → core_person; manager_id → core_person req; site_id → core_site; start_date date; finish_date date; baseline_finish date; health text: green, amber or red; budget_total money; currency text; calendar_id → core_calendar |
The eleven capabilities
Each capability carries a tier. MVP must exist for a business to cancel its incumbent subscription with confidence. STD is parity with mainstream tools. BIC marks a best-in-class difference. MVP and STD are required for the course; BIC is advanced work, except earned value, which this course requires even though the specification tiers it BIC.
| Capability | What it does | Tier |
|---|---|---|
| Intake and portfolio | Project requests with a business case, a scoring model (value, risk, strategic fit), approval, portfolio roll-up and a capacity check | STD |
| WBS and scheduling | Hierarchical WBS, durations, calendars, four dependency types with lag, auto-scheduling, CPM (early and late dates, total float, critical path) | MVP |
| Baselines and variance | Multiple baselines; start, finish and duration variance; slip alerts | STD |
| Views | Gantt with dependency lines, board, list, calendar and a portfolio timeline | MVP |
| Resources and time | Assignments with units, capacity heat map, over-allocation, timesheets with approval | STD |
| Budget and cost | Budget lines by cost code, commitments from POs, actuals, forecast at completion, contingency drawdown | STD |
| Earned value | PV, EV, AC, SPI, CPI, EAC and VAC snapshots | BIC |
| RAID and decisions | Risks, assumptions, issues, decisions, dependencies and changes, with scoring and owners | MVP |
| Punch lists and C&Q | Punch items by area or equipment with category and photo; commissioning and qualification checklists through M01 | STD |
| Status reporting | Periodic reports with health, generated summaries (AI-assisted, human-approved) and stakeholder distribution | STD |
| Templates | Kaizen event, CAPEX, NPI (APQP), shutdown and IT project templates | STD |
Intake: from a request to an approved project
Work starts as a request (prj_request) with a problem statement, expected benefits, an estimated cost and an estimated benefit. A request is scored on three things: value, risk and strategic fit. The portfolio's scoring_model holds the weights; the request's scores hold the points; total_score is the weighted sum. A portfolio roll-up then shows the whole list ranked, with a capacity check against the people available.
A request moves through submitted, under_review and approved or rejected. An approved request is converted: a prj_project is created and the request keeps its project_id. The project then moves through proposed, approved, planning, executing, closing and closed, and can also be on_hold or cancelled. Its gate (idea, define, plan, execute, close) says which review stage it has passed; status says what is happening now.
In this course the sponsor, the person who pays for the work and decides whether it starts, decides a request that is under review, and cannot decide one they raised themselves. The project manager is named on the project (manager_id) and plans it but does not approve it. Chapter 4 lists every role.
Templates and the eight types
The project type (capex, improvement, kaizen, npi, it, shutdown, customer, regulatory) chooses the template: a kaizen event, a CAPEX project, an NPI project following APQP, a shutdown or an IT project. A template is a ready-made set of tasks, dependencies and gates, so a new project starts from the way the business already does that kind of work.
Knowledge check
Weights are value 0.5, risk 0.3 and strategic fit 0.2. A request scores value 6, risk 8 (10 is the lowest risk) and fit 5. What is its total_score?
Knowledge check
A sponsor raised a request and it is under review. Who may approve it in this course?
You need: The project or task tool you use today, or a spreadsheet or whiteboard if you have none, and a notebook
Do this with the tool you would replace. Use real projects; a list of ten is enough.
Outcome: A one-page inventory: ten projects with type, stage, manager and sponsor, how requests are decided, the yearly cost of the tool and the first project to move.
References
- ISO 21502:2020, Project, programme and portfolio management: guidance on project management. https://www.iso.org/standard/74947.html
- PMI: The PMBOK Guide and standards for project management. https://www.pmi.org/standards/pmbok
- Gitnux: planning project management software. https://gitnux.org/best/planning-project-management-software/
Chapter 2 · Scheduling
The work breakdown and the critical path
A schedule is a network of tasks with rules about which can start or finish before which. This chapter builds a work breakdown, adds all four kinds of link and works the forward and backward pass by hand, so you can check any tool's critical path.
25 min4 dependency types2 passesFloat and the critical path
By the end of this chapter you can
- Build a work breakdown structure with summary tasks, tasks, gates and milestones.
- Choose among FS, SS, FF and SF with a lag, and say what each one forces.
- Work the forward and backward pass on a small network and find the float and the critical path.
A work breakdown structure
A work breakdown structure (WBS) divides the project into smaller and smaller pieces until each piece can be assigned, estimated and tracked. In the module it is the table prj_task. Each row has a wbs_code such as 1.2.1, a parent_id that points at the row above it, and a task_type: task (work to do), summary (a heading that rolls up the dates of the tasks below it), milestone (a date with no duration) or gate (a decision point).
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_task | One WBS element, with the dates the schedule engine calculates | project_id → prj_project req; parent_id → prj_task; wbs_code text req; name text req; task_type text req: task, milestone, summary or gate; duration_days num; start_at date; finish_at date; early_start, early_finish, late_start, late_finish date; total_float_days num; is_critical bool; percent_complete num; assignee_id → core_person; status text req: not_started, in_progress, blocked, done or cancelled; effort_hours num |
| prj_dependency | A link between two tasks, with its type and lag | predecessor_id → prj_task req; successor_id → prj_task req; dep_type text req: fs, ss, ff or sf; lag_days num |
The duration counts working days on the project's calendar (calendar_id, pointing at core_calendar), so a task that is five days long does not finish in the middle of a weekend. The work behind it is a different number: effort_hours is the person-hours of work, and a five-day task can take eighty hours if two people work on it full time.
Four dependency types, with lag
A dependency says that task B (the successor) is held back by task A (the predecessor). Finish to start (FS) is the one most tools start with: B cannot start until A finishes. The module also supports start to start (SS), finish to finish (FF) and start to finish (SF) [1][2]. A lag adds waiting time: a concrete pour followed by two days of curing is FS with a lag of 2. A negative lag, called a lead in scheduling practice, lets B begin before A ends.
| Type | Rule for the successor | A plain example |
|---|---|---|
| FS | Start no earlier than the predecessor's finish + lag | Paint the room after the plaster has dried (FS, lag 2) |
| SS | Start no earlier than the predecessor's start + lag | Begin the site survey one day after digging begins (SS, lag 1) |
| FF | Finish no earlier than the predecessor's finish + lag | Final testing cannot finish until the last repair is finished (FF) |
| SF | Finish no earlier than the predecessor's start + lag | The old system cannot be switched off until the new one has started (SF) |
The critical path method
The critical path method (CPM) finds which tasks decide the finish date. It makes two passes through the network, working in whole days from the start. The convention in this chapter is that the project starts at 0 and a task's finish is its start plus its duration.
- Forward pass. A task's early start (ES) is the latest early finish among its predecessors (0 for the first task). Its early finish (EF) is ES + duration.
- Backward pass. Start from the project finish. A task's late finish (LF) is the earliest late start among its successors (the project finish for the last task). Its late start (LS) is LF − duration.
- Total float is LS − ES (the same as LF − EF): how many days the task can slip before the finish date moves.
- A task with zero float is critical (is_critical), and the chain of critical tasks is the critical path.
| Task | Duration | Predecessors | ES | EF | LS | LF | Float |
|---|---|---|---|---|---|---|---|
| A | 3 | none | 0 | 3 | 0 | 3 | 0 |
| B | 4 | A | 3 | 7 | 3 | 7 | 0 |
| C | 2 | A | 3 | 5 | 7 | 9 | 4 |
| D | 5 | B | 7 | 12 | 7 | 12 | 0 |
| E | 3 | C | 5 | 8 | 9 | 12 | 4 |
| F | 2 | D, E | 12 | 14 | 12 | 14 | 0 |
The critical path is A, B, D and F: 3 + 4 + 5 + 2 = 14 days. Anything that delays one of them delays the finish by the same amount. C and E can slip by up to four days each before they matter, and C and E together share that four, because they sit on the same chain.
A Gantt chart is one view of this data. The module also gives a board (tasks by status), a list, a calendar and a portfolio timeline of projects. All of them read the same prj_task rows, so a change made in one view appears in the others.
Auto-scheduling and slip
The schedule engine recomputes the dates whenever a duration, a link or a lag changes. This is a test the module must pass: moving a predecessor reschedules its successors and recomputes the critical path, and the CPM results must match a worked network from a textbook such as the PMBOK. The six-task network above is a teaching network of our own, consistent with CPM textbooks, and the course's engine tests use it. When a critical task slips past its baseline, the module publishes the event prj.task.critical_slip so the right people hear about it at once.
Knowledge check
In the network above, task C has an early start of 3, a duration of 2 and a late finish of 9. What is its total float?
Knowledge check
The commissioning test cannot be finished until the last repair has been finished. Which link fits?
You need: A spreadsheet or paper, and the project you chose in chapter 1 with the dates your current tool shows
Pick a project with eight to twelve tasks. If your tool shows a critical path, keep that view open to compare.
Outcome: A table of ES, EF, LS, LF and float for every task, a critical path agreed or explained against your tool, and a note of what moved when each task slipped.
References
- PMI: standards, including the Practice Standard for Scheduling. https://www.pmi.org/standards
- ClickUp: Gantt features compared (dependency types, critical path, baselines). https://clickup.com/learn/topic/project-management/tools/features/gantt-charts/
- Smartsheet: best Gantt chart software. https://www.smartsheet.com/content/best-gantt-chart-software
Chapter 3 · Control
Baselines, budgets and earned value
A plan is only useful if you can see how far reality has moved from it. This chapter saves baselines to measure slip, tracks money from budget to commitment to actual to forecast, and uses earned value to say, in two numbers, whether a project is on time and on cost.
25 minBaseline = a saved copy4 money columns7 earned value measures
By the end of this chapter you can
- Save a baseline and measure start, finish and duration variance against it.
- Show budget, committed, actual and forecast for each cost code, and explain why commitments come from purchase orders.
- Calculate PV, EV, AC, SPI, CPI, EAC and VAC from a small example.
Baselines: the plan you measure against
A baseline is a saved copy of the plan at a moment in time. In the module it is a row in prj_baseline with a name, the moment it was captured (captured_at) and the plan itself in a json column (data). A project can have several baselines: an approved one, and later ones after an approved change. Once saved, a baseline is never edited. A change is a new baseline.
Variance is the current value minus the baseline value: start variance, finish variance and duration variance. A positive finish variance means late. The project row keeps baseline_finish beside finish_date. The module shows the slip and raises an alert when a critical task slips. One KPI follows from it: milestone hit rate, the milestones met on their baseline date divided by the milestones due.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_baseline | A saved snapshot of the plan | project_id → prj_project req; name text req; captured_at ts req; data json req |
Budget lines, commitments, actuals and forecast
Money is tracked by cost code in prj_budget_line. Each line has a category (equipment, installation, engineering, construction, validation, contingency, internal_labor, software or other) and four amounts: the budget approved, the amount committed (promised to suppliers on approved purchase orders, whether or not it has been invoiced or paid yet, so actual is part of committed, not added to it), the actual spent, and the forecast at completion. All of them are stored as integer cents and converted only when shown.
The rows in prj_commitment come from purchase orders. When the purchasing module (M12) approves a PO, the event inv.po.approved reaches this module, and a commitment is created against a budget line with the vendor, the amount and the date. The po_ref column is a soft link: it names the PO without a database-enforced foreign key, so the module still works when M12 is not installed. The course builds the pull version of the same rule first: in Session 4 a route reads the open PO lines when the manager runs it, and consuming the event as it arrives is the later refinement. The rule to test: the committed amount on a budget line equals the sum of its linked open PO lines.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_budget_line | A budget by cost code, with committed, actual and forecast | project_id → prj_project req; cost_code text req; category text req: equipment, installation, engineering, construction, validation, contingency, internal_labor, software or other; budget_amount money req; committed_amount money; actual_amount money; forecast_amount money |
| prj_commitment | An amount promised through a purchase order | project_id → prj_project req; budget_line_id → prj_budget_line; po_ref → inv_purchase_order (soft link); vendor_party_id → core_party; amount money req; committed_at date |
The KPI for money is budget variance = (forecast − budget) ÷ budget. In the chart above, 765 against 750 is 2.0%. A contingency line is drawn down as risks happen: spend that was not planned is charged to contingency, and the remaining contingency is the cushion for what is still to come. When a business needs to say how firm a number is, AACE International's Recommended Practice 18R-97 classifies cost estimates from Class 5 (least defined, earliest) to Class 1 (most defined) [1]. No column holds the class, so record it in the ext column of the budget line (for example ext.estimate_class) when you record the budget.
Earned value
Earned value management (EVM) compares three numbers at one date. Planned value (PV) is the budgeted cost of the work that should be finished by now. Earned value (EV) is the budgeted cost of the work actually finished (percent complete × budget). Actual cost (AC) is what has been spent. The PMBOK Guide explains these measures for general project work [2]. The module specification also names ANSI/EIA-748, the guidelines for an earned value management system built on them; read it from its publisher if a contract asks you to follow it.
| Measure | Formula | Reads as |
|---|---|---|
| Schedule performance index (SPI) | EV ÷ PV | Below 1: behind schedule. Above 1: ahead. |
| Cost performance index (CPI) | EV ÷ AC | Below 1: over cost. Above 1: under cost. |
| Estimate at completion (EAC) | BAC ÷ CPI (if today's cost performance continues) | What the project is likely to cost in all. |
| Variance at completion (VAC) | BAC − EAC | Negative: a forecast overrun. |
BAC, the budget at completion, is the total budget. Because money is stored in integer cents, compute the EAC as BAC × AC ÷ EV and round once at the end, not BAC ÷ CPI with a rounded CPI.
Each reading is stored as a row in prj_evm_snapshot with its date (as_of), so the history of SPI and CPI can be charted and an old reading is never recalculated.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_evm_snapshot | An earned value reading on one date | project_id → prj_project req; as_of date req; pv money; ev money; ac money; spi num; cpi num; eac money; vac money |
Knowledge check
At the status date, PV is 400 and EV is 360. What is SPI, and what does it say?
Knowledge check
EV is 150 and AC is 120. What is CPI?
Knowledge check
A line has a budget of 400 and 150 spent, with 380 committed on approved POs. What does a report showing only actuals miss?
You need: A spreadsheet, one project with a budget, and its purchase orders and invoices to date
Use the project you worked in chapter 2. If you have no purchase orders, use the supplier quotes you accepted.
Outcome: A one-page table of budget, committed, actual and forecast by cost code, a budget variance, and SPI, CPI, EAC and VAC with a one-sentence reading.
References
- AACE International (home page; publisher of Recommended Practice 18R-97, cost estimate classification). https://www.aacei.org/
- PMI: the PMBOK Guide, including earned value and cost management. https://www.pmi.org/standards/pmbok
- ClickUp: Gantt features compared (dependency types, critical path, baselines). https://clickup.com/learn/topic/project-management/tools/features/gantt-charts/
Chapter 4 · Running the project
People, risk, handover and cut-over
A project is run by people, and its risks, punch items and reports are records like any other. This chapter covers who may do what, timesheets, the RAID log, punch lists and commissioning, status reports, and how a Smartsheet, Asana, monday.com or MS Project export becomes a baseline in the new module.
20 min7 roles6 RAID types3 punch categories
By the end of this chapter you can
- Say which role may request, plan, work, approve and read, and why a person never approves their own hours.
- Score a RAID item and follow a punch item and a status report from start to finish.
- Plan the cut-over from the old tool and say how the comparison is made.
Who does what
Access is decided by jobs, not names. Six are people and one is a service role. The rights each one needs are written in the Role and Exposure Matrix in Session 3, and the table shows what each needs to do.
| Role | What they do | Where they work |
|---|---|---|
| Project manager | Plans and controls a project: tasks, links, budgets, RAID, punch lists and status reports | Gantt, board, list, calendar |
| Sponsor | Pays for the work; decides requests and whether a project starts or is cancelled; records decisions in the RAID log | Portfolio timeline, approvals |
| Task assignee | Does the tasks; updates status and percent complete; works punch items | Phone screen |
| Team member who logs time | Logs and submits hours; sees only their own | Phone screen |
| Approver of timesheets and budgets | Approves or rejects hours and budget steps, never their own | Approvals queue |
| Stakeholder | Reads published status reports and milestone dates; asks questions | Read-only view |
| Cut-over importer (service role) | Loads the old tool's projects once, then is switched off | No screen |
Resources and timesheets
An assignment (prj_assignment) gives a person a task with units_pct, the share of their working time: 50 means half. Planned hours and actual hours sit beside it. Adding a person's units across all the tasks on a day shows their load, and a total above 100 is over-allocation. A capacity heat map shows the same sums across a team and a period.
Hours are logged in prj_time_entry: a person, a project, optionally a task, a work date, hours, a billable flag and a status. A new entry is a draft. The team member submits it. An approver then approves or rejects it, and a rejected entry stays rejected, with the reason, until the team member edits it and submits it again (there is no step back to draft). Hours count only once approved, and the approved hours roll up onto the assignment's actual hours.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_assignment | A person assigned to a task | task_id → prj_task req; person_id → core_person req; units_pct num req; planned_hours num; actual_hours num |
| prj_time_entry | One timesheet entry | person_id → core_person req; project_id → prj_project req; task_id → prj_task; work_date date req; hours num req; billable bool; status text req: draft, submitted, approved or rejected; approved_by → core_person |
RAID: risks, assumptions, issues and decisions
The RAID log is one table, prj_raid_item, with a raid_type of risk, assumption, issue, decision, dependency or change. Each has a title, a description, an owner, a due date, a response and a status of open, monitoring or closed. A risk or an issue also carries a probability and an impact, and the score is their product.
Punch lists and commissioning
A punch item (prj_punch_item) is a small thing found on a walk-down that must be fixed before handover: a missing label, a scratched panel, a loose cover. It names the equipment, has a category (a, b or c), a photo, an owner and a due date. The person fixing it moves it to in_progress and then to ready_to_verify. Someone else checks it and closes it, which publishes prj.punch_item.closed.
For a regulated capital project, the module links commissioning and qualification (C&Q) checklists to the project. The checklists themselves are forms built in M01. The evidence, meaning the completed checklists and the signatures, must be kept with the project record and handed to Document Control when the project closes. ISPE's Baseline Guide Volume 5 and ASTM E2500 describe commissioning and qualification for regulated facilities [1][2].
Status reports
A status report (prj_status_report) is written for a period end. It has a health of green, amber or red, a summary, accomplishments, next steps and a risks summary, and an author. The module can draft the summary from the project's data, but a person reads it, edits it and approves it before it is published. Publishing sets published_at, sends the report to stakeholders and publishes prj.status_report.published. In this course a published report never changes: a correction is a new report.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| prj_raid_item | A risk, assumption, issue, decision, dependency or change | project_id → prj_project req; raid_type text req: risk, assumption, issue, decision, dependency or change; title text req; description text; probability int; impact int; score int; owner_id → core_person; due_at date; response text; status text req: open, monitoring or closed |
| prj_status_report | A periodic status report | project_id → prj_project req; period_end date req; health text req: green, amber or red; summary text req; accomplishments text; next_steps text; risks_summary text; author_id → core_person; published_at ts |
| prj_punch_item | A punch list item | project_id → prj_project req; equipment_id → core_equipment; description text req; category text req: a, b or c; raised_by → core_person; assigned_to → core_person; due_at date; status text req: open, in_progress, ready_to_verify or closed; photo_attachment_id → core_attachment; closed_at ts |
Move the data, run both, retire the old tool
Smartsheet, Asana, monday.com and Microsoft Project can each export tasks with dates and dependencies, as CSV or XML. For how such tools handle links and baselines, see [3]. The module imports that file as a new baseline: the old tool's plan is saved, not mixed into the live plan, so the two can be compared. Then run the old and new tools side by side for at least one week and compare task dates, the critical path, the committed amounts and earned value. When they agree, cut over, and cancel the old subscription with the saving recorded.
| KPI | Definition |
|---|---|
| Schedule performance (SPI) | EV ÷ PV |
| Cost performance (CPI) | EV ÷ AC |
| Milestone hit rate | Milestones met on their baseline date ÷ milestones due |
| Budget variance | (Forecast − budget) ÷ budget |
| Open punch items by category | A, B and C items outstanding at handover |
Knowledge check
A risk has probability 4 and impact 5. What is its score, using probability × impact?
Knowledge check
An approver finds that a time entry in their queue is their own. What should the module do?
Knowledge check
An old tool's export is imported. How does the module hold it?
You need: Your current tool, with permission to export, and the inventory from chapter 1
Do not cancel anything in this exercise. It prepares the comparison.
Outcome: An export of one real project, a side-by-side plan with a named person and four checks with accepted differences, and the renewal date and cost of the old tool.
References
- ISPE (home page; publisher of Baseline Guide Volume 5, Commissioning and Qualification). https://ispe.org/
- ASTM E2500: Standard Guide for Specification, Design, and Verification of Pharmaceutical and Biopharmaceutical Manufacturing Systems and Equipment. https://store.astm.org/e2500-20.html
- Smartsheet: best Gantt chart software. https://www.smartsheet.com/content/best-gantt-chart-software
Chapter 5 · 12 questions · 80% passes
Final assessment
Twelve questions across the element. Score 80% (10 of 12) to pass. Your LMS records your score and each answer; you can review the chapters and try again.
15 min12 questions≈ 15 minutesRetake allowed
Your result
CivOps AI Academy
Projects, Jobs and Tasks: Portfolio, Schedule, Cost and Earned Value as Governed Records
Element M20 complete · Learner
Your LMS records this completion. For the CivOps Foundation certificate, finish the Foundation Course at https://civops.io/learn.